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: Add performance measurements #1264

Merged
merged 11 commits into from
Jan 17, 2025
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
10 changes: 10 additions & 0 deletions __mocks__/react-native-performance-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import RNPerformance from 'react-native-performance'

export const mockRNPerformance: jest.Mocked<typeof RNPerformance> = {
mark: jest.fn(),
measure: jest.fn(),
default: {
mark: jest.fn(),
measure: jest.fn(),
}
} as unknown as jest.Mocked<typeof RNPerformance>
2 changes: 2 additions & 0 deletions __tests__/jestSetupFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { mockRNBackgroundGeolocation } from '../__mocks__/react-native-backgroun
import { mockRNFS } from '../__mocks__/react-native-fs-mock'
import { mockRNIAP } from '../__mocks__/react-native-iap-mock'
import { mockRNInAppBrowser } from '../__mocks__/react-native-inappbrowser-reborn-mock'
import { mockRNPerformance } from '../__mocks__/react-native-performance-mock'

jest.mock('react-native-device-info', () => mockRNDeviceInfo)

Expand All @@ -29,6 +30,7 @@ jest.mock('@sentry/react-native', () => ({
jest.mock('react-native-fs', () => mockRNFS)
jest.mock('react-native-iap', () => mockRNIAP)
jest.mock('react-native-inappbrowser-reborn', () => mockRNInAppBrowser)
jest.mock('react-native-performance', () => mockRNPerformance)
jest.mock('react-native-ios11-devicecheck', () => ({
getToken: jest.fn()
}))
Expand Down
114 changes: 114 additions & 0 deletions docs/how-to-debug-performances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# How to debug performances

Since [#1264](https://github.com/cozy/cozy-flagship-app/pull/1264) it is possible to measure the Flagship App's performances.

This document's goal is to describe how to retrieve measurements, how to read them and how to add new measurements.

The performance measurements are based on `react-native-performances` plugin and are complementary to CozyClient performance API implementation. More about those two concept can be read here:
- `react-native-performances`: https://github.com/oblador/react-native-performance
- CozyClient Performance API: https://github.com/cozy/cozy-client/blob/master/docs/performances.md

# Nomenclature

- `PerformanceAPI`: The API provided by CozyClient for measuring timings in the code base
- `Performance devtools`: Browsers devtools pane dedicated to performance (i.e. [Chrome devtools](https://developer.chrome.com/docs/devtools/performance) and [Firefox profiled](https://profiler.firefox.com/docs/#/))
- `Flipper performances plugin`: Plugin for Flipper dedicated to performance readings
- `Mark`: A temporal event
- `Measure`: A timing measurement between two temporal events

# Retrieve measurements

There are two way to retrieve measurements:

- In realtime using Flipper
- Asynchronously using Universal Links

## Flipper plugin

Flipper allows to read performance measurements in realtime through the `flipper-plugin-performance` plugin:

- Open Flipper
- Install the plugin `flipper-plugin-performance`
- Start the Flagship app in development mode
- On Flipper, open the `Performance` pane

![Screenshot of the Flipper plugin](./images/performances-flipper.png)

## Universal Links

When it is not possible to use the Flipper plugin, then the Flagship App allows to share performances measurements by email.

To do this:

- Open the Flagship app and do the scenario that needs to be measured
- Open one of the following links:
- https://links.mycozy.cloud/flagship/sendperfs
- cozy://sendperfs

When clicked, the Flagship App will upload the performance measurements for the current session in the user's Cozy and then an email intent will allow to send a link to download them.

The measurements are uploaded into the `Settings/AALogs/<current_date>` folder.

# Read the measurements

When downloading the measurements that were generated through Universal Link, then it is possible to read them using Chromium devtools.

To do this:

- Download the measurements file
- From the `cozy-flagship-app` project, run the following command:
- `yarn perf:convert <path_to_downloaded_file>`
- This will generate a new file in the same folder as the targeted file, and suffixed with `_converted`
- In a Chromium browser, open the devtools
- Open the `Performance` pane
- In the upper-left of the pane, click on `Load profile...` and selected the `_converted` file

![Screenshot of the Chromium devtools](./images/performances-devtools.png)

# Add new measurements

In order to add new measurements, it is possible to use the API from `/app/domain/performances/measure`.

This API provides `mark()` and `measure()` methods that are the core concept of doing measurements.

Those methods have the same behavior as the ones available in CozyClient. Their API and behaviour are described in [the related documentation](https://github.com/cozy/cozy-client/blob/master/docs/performances.md#mark-method)

## Add new measurements in a method

In order to measure the duration of a methods or part of a methods:

```js
import rnperformance from '/app/domain/performances/measure'

const someMethod = () => {
const markName = rnperformance.mark('someMethod')

// ... Method code

rnperformance.measure({ markName })
}
```


## Add new measurements in a React component

In order to measure the duration of a React component initialization:

```js
import rnperformance from '/app/domain/performances/measure'

const SomeReactComponent = () => {
const [markName] = useState(() => rnperformance.mark('SomeReactComponent'))

useEffect(() => {
rnperformance.measure({
markName: markName,
measureName: 'Mount <App />'
})
}, [markName])
}
```

## Add new measurements in CozyClient

See [related documentation](https://github.com/cozy/cozy-client/blob/master/docs/performances.md)
Binary file added docs/images/performances-devtools.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/performances-flipper.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,8 @@ PODS:
- React
- react-native-netinfo (9.5.0):
- React-Core
- react-native-performance (5.1.2):
- React-Core
- react-native-print (0.11.0):
- React-Core
- react-native-quick-base64 (2.1.2):
Expand Down Expand Up @@ -1425,6 +1427,7 @@ DEPENDENCIES:
- react-native-mail (from `../node_modules/react-native-mail`)
- react-native-mlkit-ocr (from `../node_modules/react-native-mlkit-ocr`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-performance (from `../node_modules/react-native-performance`)
- react-native-print (from `../node_modules/react-native-print`)
- react-native-quick-base64 (from `../node_modules/react-native-quick-base64`)
- react-native-quick-sqlite (from `../node_modules/react-native-quick-sqlite`)
Expand Down Expand Up @@ -1604,6 +1607,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-mlkit-ocr"
react-native-netinfo:
:path: "../node_modules/@react-native-community/netinfo"
react-native-performance:
:path: "../node_modules/react-native-performance"
react-native-print:
:path: "../node_modules/react-native-print"
react-native-quick-base64:
Expand Down Expand Up @@ -1788,6 +1793,7 @@ SPEC CHECKSUMS:
react-native-mail: 8fdcd3aef007c33a6877a18eb4cf7447a1d4ce4a
react-native-mlkit-ocr: 72cdbde86f8d29cba26cf9fa0a1865fe45c8f8d6
react-native-netinfo: 48c5f79a84fbc3ba1d28a8b0d04adeda72885fa8
react-native-performance: ff93f8af3b2ee9519fd7879896aa9b8b8272691d
react-native-print: f704aef52d931bfce6d1d84351dbb5232d7ecb89
react-native-quick-base64: e1ea036b3dec44c6da2439bd62881a09de614b23
react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"ios": "react-native run-ios",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"log:android": "adb logcat *:S ReactNative:V ReactNativeJS:V",
"perf:convert": "yarn tsc --project scripts && node scripts/dist/convert-perf-logs.cmd.js",
"postinstall": "patch-package && yarn install:scripts && react-native setup-ios-permissions && pod-install",
"pre-commit": "yarn lint",
"start": "react-native start",
Expand Down Expand Up @@ -56,14 +57,14 @@
"@sentry/integrations": "7.114.0",
"@sentry/react-native": "5.33.1",
"base-64": "^1.0.0",
"cozy-client": "^50.3.1",
"cozy-client": "^51.7.0",
"cozy-clisk": "^0.38.1",
"cozy-device-helper": "^2.7.0",
"cozy-flags": "^3.2.0",
"cozy-intent": "^2.23.0",
"cozy-logger": "^1.10.0",
"cozy-minilog": "3.3.1",
"cozy-pouch-link": "^50.3.1",
"cozy-pouch-link": "^51.7.0",
"date-fns": "2.29.3",
"events": "^3.3.0",
"html-entities": "^2.3.3",
Expand Down Expand Up @@ -112,6 +113,8 @@
"react-native-mail": "^6.1.1",
"react-native-mask-input": "1.2.1",
"react-native-mlkit-ocr": "^0.3.0",
"react-native-performance": "^5.1.2",
"react-native-performance-flipper-reporter": "^5.0.0",
"react-native-permissions": "^3.9.3",
"react-native-play-install-referrer": "^1.1.8",
"react-native-print": "0.11.0",
Expand Down
13 changes: 13 additions & 0 deletions patches/react-native-performance-flipper-reporter+5.0.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/node_modules/react-native-performance-flipper-reporter/src/index.js b/node_modules/react-native-performance-flipper-reporter/src/index.js
index e99d526..bf1f6f1 100644
--- a/node_modules/react-native-performance-flipper-reporter/src/index.js
+++ b/node_modules/react-native-performance-flipper-reporter/src/index.js
@@ -163,7 +163,7 @@ export function setupDefaultFlipperReporter() {
name: entry.name,
startTime: entry.startTime,
duration: entry.duration,
- category: 'App',
+ category: entry.detail?.category || 'App',
}))
);
},
61 changes: 61 additions & 0 deletions patches/redux+4.2.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
diff --git a/node_modules/redux/lib/redux.js b/node_modules/redux/lib/redux.js
index 8a39141..752ab8e 100644
--- a/node_modules/redux/lib/redux.js
+++ b/node_modules/redux/lib/redux.js
@@ -3,6 +3,11 @@
Object.defineProperty(exports, '__esModule', { value: true });

var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
+// Cozy override: Add react-native-performance measurements
+//*
+var uniqueId = require('lodash/uniqueId');
+var rnperformance = require('react-native-performance').default;
+//*/

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

@@ -279,6 +284,11 @@ function createStore(reducer, preloadedState, enhancer) {


function dispatch(action) {
+ // Cozy override: Add react-native-performance measurements
+ //*
+ const markName = `dispatch ${uniqueId()}`
+ rnperformance.mark(markName)
+ //*/
if (!isPlainObject(action)) {
throw new Error(process.env.NODE_ENV === "production" ? formatProdErrorMessage(7) : "Actions must be plain objects. Instead, the actual type was: '" + kindOf(action) + "'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.");
}
@@ -302,9 +312,32 @@ function createStore(reducer, preloadedState, enhancer) {

for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
+ // Cozy override: Add react-native-performance measurements
+ //*
+ const markNamecurrentReducer = `dispatchlistener ${uniqueId()}`
+ rnperformance.mark(markNamecurrentReducer)
+ //*/
listener();
+ // Cozy override: Add react-native-performance measurements
+ //*
+ rnperformance.measure(markNamecurrentReducer, {
+ start: markNamecurrentReducer,
+ detail: {
+ category: 'REDUX'
+ }
+ })
+ //*/
}

+ // Cozy override: Add react-native-performance measurements
+ //*
+ rnperformance.measure(markName, {
+ start: markName,
+ detail: {
+ category: 'REDUX'
+ }
+ })
+ //*/
return action;
}
/**
Loading
Loading