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

feat: web profiler v2 #46133

Merged
merged 4 commits into from
Aug 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
12 changes: 12 additions & 0 deletions .github/workflows/platformDeploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}

- name: Archive desktop sourcemaps
uses: actions/upload-artifact@v4
with:
name: desktop-sourcemap-${{ github.ref_name }}
path: desktop/dist/www/merged-source-map.js.map

iOS:
name: Build and deploy iOS
needs: validateActor
Expand Down Expand Up @@ -347,6 +353,12 @@ jobs:
env:
S3_URL: s3://${{ env.SHOULD_DEPLOY_PRODUCTION != 'true' && 'staging-' || '' }}expensify-cash

- name: Archive web sourcemaps
uses: actions/upload-artifact@v4
with:
name: web-sourcemap-${{ github.ref_name }}
path: dist/merged-source-map.js.map

- name: Purge Cloudflare cache
run: /home/runner/.local/bin/cli4 --verbose --delete hosts=["${{ env.SHOULD_DEPLOY_PRODUCTION != 'true' && 'staging.' || '' }}new.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache
env:
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,20 @@ Within Xcode head to the build phase - `Bundle React Native code and images`.
```jsx
npm i && npm run pod-install
```
7. Depending on the platform you are targeting, run your Android/iOS app in production mode.
8. Upon completion, the generated source map can be found at:
4. Depending on the platform you are targeting, run your Android/iOS app in production mode.
5. Upon completion, the generated source map can be found at:
Android: `android/app/build/generated/sourcemaps/react/productionRelease/index.android.bundle.map`
IOS: `main.jsbundle.map`
web: `dist/merged-source-map.js.map`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noob question: Why haven't we added the desktop/dist/www/merged-source-map.js.map path here for desktop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jayeshmangwani I think because the code for web and desktop is identical. At least when I was trying to profile desktop app using web source map everything was well, i. e. I could see all stack-traces correctly


### Recording a Trace:
1. Ensure you have generated the source map as outlined above.
2. Launch the app in production mode.
2. Navigate to the feature you wish to profile.
3. Initiate the profiling session by tapping with four fingers to open the menu and selecting **`Use Profiling`**.
4. Close the menu and interact with the app.
5. After completing your interactions, tap with four fingers again and select to stop profiling.
6. You will be presented with a **`Share`** option to export the trace, which includes a trace file (`Profile<app version>.cpuprofile`) and build info (`AppInfo<app version>.json`).
3. Navigate to the feature you wish to profile.
4. Initiate the profiling session by tapping with four fingers (on mobile) or `cmd+d` (on web) to open the menu and selecting **`Use Profiling`**.
5. Close the menu and interact with the app.
6. After completing your interactions, tap with four fingers or `cmd+d` again and select to stop profiling.
7. You will be presented with a **`Share`** option to export the trace, which includes a trace file (`Profile<app version>.cpuprofile`) and build info (`AppInfo<app version>.json`).

Build info:
```jsx
Expand All @@ -265,6 +266,7 @@ Build info:
4. Use the following commands to symbolicate the trace for Android and iOS, respectively:
Android: `npm run symbolicate-release:android`
IOS: `npm run symbolicate-release:ios`
web: `npm run symbolicate-release:web`
5. A new file named `Profile_trace_for_<app version>-converted.json` will appear in your project's root folder.
6. Open this file in your tool of choice:
- SpeedScope ([https://www.speedscope.app](https://www.speedscope.app/))
Expand Down
4 changes: 4 additions & 0 deletions config/webpack/webpack.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ const getConfiguration = (environment: Environment): Promise<Configuration> =>
cert: path.join(__dirname, 'certificate.pem'),
},
},
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'Document-Policy': 'js-profiling',
},
},
plugins: [
new DefinePlugin({
Expand Down
108 changes: 108 additions & 0 deletions desktop/electron-serve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-disable @typescript-eslint/no-misused-promises */

/* eslint-disable rulesdir/no-negated-variables */

/* eslint-disable @lwc/lwc/no-async-await */

/**
* This file is a modified version of the electron-serve package.
* We keep the same interface, but instead of file protocol we use buffer protocol (with support of JS self profiling).
*/
import type {BrowserWindow, Protocol} from 'electron';
import {app, protocol, session} from 'electron';
import fs from 'fs';
import mime from 'mime-types';
import path from 'path';

type RegisterBufferProtocol = Protocol['registerBufferProtocol'];
type HandlerType = Parameters<RegisterBufferProtocol>[1];
type Optional<T> = T | null | undefined;

const FILE_NOT_FOUND = -6;

const getPath = async (filePath: string): Promise<Optional<string>> => {
try {
const result = await fs.promises.stat(filePath);

if (result.isFile()) {
return filePath;
}

if (result.isDirectory()) {
// eslint-disable-next-line @typescript-eslint/return-await
return getPath(path.join(filePath, 'index.html'));
}
} catch {
return null;
}
};

type ServeOptions = {
directory: string;
isCorsEnabled?: boolean;
scheme?: string;
hostname?: string;
file?: string;
partition?: string;
};

export default function electronServe(options: ServeOptions) {
const mandatoryOptions = {
isCorsEnabled: true,
scheme: 'app',
hostname: '-',
file: 'index',
...options,
};

if (!mandatoryOptions.directory) {
throw new Error('The `directory` option is required');
}

mandatoryOptions.directory = path.resolve(app.getAppPath(), mandatoryOptions.directory);

const handler: HandlerType = async (request, callback) => {
const filePath = path.join(mandatoryOptions.directory, decodeURIComponent(new URL(request.url).pathname));
const resolvedPath = (await getPath(filePath)) ?? path.join(mandatoryOptions.directory, `${mandatoryOptions.file}.html`);
const mimeType = mime.lookup(resolvedPath) || 'application/octet-stream';

try {
const data = await fs.promises.readFile(resolvedPath);
callback({
mimeType,
data: Buffer.from(data),
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'Document-Policy': 'js-profiling',
},
});
} catch (error) {
callback({error: FILE_NOT_FOUND});
}
};

protocol.registerSchemesAsPrivileged([
{
scheme: mandatoryOptions.scheme,
privileges: {
standard: true,
secure: true,
allowServiceWorkers: true,
supportFetchAPI: true,
corsEnabled: mandatoryOptions.isCorsEnabled,
},
},
]);

app.on('ready', () => {
const partitionSession = mandatoryOptions.partition ? session.fromPartition(mandatoryOptions.partition) : session.defaultSession;

partitionSession.protocol.registerBufferProtocol(mandatoryOptions.scheme, handler);
});

// eslint-disable-next-line @typescript-eslint/naming-convention
return async (window_: BrowserWindow, searchParameters?: URLSearchParams) => {
const queryString = searchParameters ? `?${new URLSearchParams(searchParameters).toString()}` : '';
await window_.loadURL(`${mandatoryOptions.scheme}://${mandatoryOptions.hostname}${queryString}`);
};
}
2 changes: 1 addition & 1 deletion desktop/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {BrowserView, MenuItem, MenuItemConstructorOptions, WebContents, Web
import contextMenu from 'electron-context-menu';
import log from 'electron-log';
import type {ElectronLog} from 'electron-log';
import serve from 'electron-serve';
import {autoUpdater} from 'electron-updater';
import {machineId} from 'node-machine-id';
import checkForUpdates from '@libs/checkForUpdates';
Expand All @@ -14,6 +13,7 @@ import type {TranslationPaths} from '@src/languages/types';
import type PlatformSpecificUpdater from '@src/setup/platformSetup/types';
import type {Locale} from '@src/types/onyx';
import type {CreateDownloadQueueModule, DownloadItem} from './createDownloadQueue';
import serve from './electron-serve';
import ELECTRON_EVENTS from './ELECTRON_EVENTS';

const createDownloadQueue = require<CreateDownloadQueueModule>('./createDownloadQueue').default;
Expand Down
64 changes: 41 additions & 23 deletions desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
"dependencies": {
"electron-context-menu": "^2.3.0",
"electron-log": "^4.4.8",
"electron-serve": "^1.3.0",
"electron-updater": "^6.3.2",
"mime-types": "^2.1.35",
"node-machine-id": "^1.1.12"
},
"author": "Expensify, Inc.",
"license": "MIT",
"private": true
"private": true,
"devDependencies": {
"@types/mime-types": "^2.1.4"
}
}
Loading
Loading