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: release-profiler on web #44432

Merged
merged 21 commits into from
Jul 22, 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@v3
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 @@ -348,6 +354,12 @@ jobs:
env:
S3_URL: s3://${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && '' || 'staging-' }}expensify-cash

- name: Archive web sourcemaps
uses: actions/upload-artifact@v3
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=["${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) && '' || '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`

### 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
106 changes: 106 additions & 0 deletions desktop/electron-serve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* 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 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`);

try {
const data = await fs.promises.readFile(resolvedPath);
callback({
mimeType: 'text/html',
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: I believe this may have caused issue #46048 by overriding the Content-Type response header for non-html responses.

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
17 changes: 0 additions & 17 deletions desktop/package-lock.json

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

1 change: 0 additions & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"dependencies": {
"electron-context-menu": "^2.3.0",
"electron-log": "^4.4.8",
"electron-serve": "^1.3.0",
"electron-updater": "^6.2.1",
"node-machine-id": "^1.1.12"
},
Expand Down
Loading
Loading