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

Add performance metrics to the app #7953

Merged
merged 10 commits into from
Jun 12, 2024

Conversation

larkox
Copy link
Contributor

@larkox larkox commented May 14, 2024

Summary

Add performance metrics to the mobile app based on this document: https://mattermost.atlassian.net/wiki/spaces/ICU/pages/2806808578/Metrics+definition

Ticket Link

Fix https://mattermost.atlassian.net/browse/MM-58140

Related PRs

Server: mattermost/mattermost#27045

Release Note

Mobile will now send performance metrics to servers that have them enabled

@larkox larkox added 2: Dev Review Requires review by a core commiter Do Not Merge Should not be merged until this label is removed labels May 14, 2024
@larkox larkox requested a review from hmhealey May 14, 2024 16:27
@@ -73,6 +74,11 @@ function PostDraft({
setCursorPosition(message.length);
}, [channelId, rootId]);

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the impact if you use useLayoutEffect instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

useLayoutEffect will run before painting into the screen. I would prefer if the measure is done after painting. But 0/5.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok I understand.. now the next question is why do we consider the channel/thread switch complete when the post_draft renders ? out of curiosity my question, as I want to understand how did you come up with the fact that this is the last component being rendered on screen.

Also, if that is the case, is it still true on tablets ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now that you mention it, I wanted to revisit this, you are right.
No, the post draft is not the last thing that renders in a channel. I thought it might be (and was convenient) but after some tests, it may not be.
Also, I completely forgot about the tablet scenario 🤦
I will revisit this.

app/managers/performance_metrics_manager.ts Outdated Show resolved Hide resolved
app/managers/performance_metrics_manager.ts Outdated Show resolved Hide resolved
app/managers/performance_metrics_manager.ts Outdated Show resolved Hide resolved
@larkox larkox marked this pull request as ready for review May 22, 2024 10:56
types/api/config.d.ts Outdated Show resolved Hide resolved
version: '0.1.0',
labels: {
platform: Platform.select({ios: 'ios', default: 'android'}),
agent: 'rnapp',
Copy link
Member

Choose a reason for hiding this comment

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

Similar to my comment on the server PR, I don't think we need to set the agent for mobile reports

Copy link
Contributor

Choose a reason for hiding this comment

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

not for this PR, but something we should consider is to come up with a way to identify the type of device (High end, Mid, low end) as that will give us more insight with the collected numbers.

Copy link
Member

Choose a reason for hiding this comment

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

That seems like a good idea. I've wondered about similar on the web-side, but we're much more restricted in what metrics we can collect about the device there

};
}

private onAppStateChange(appState: AppStateStatus) {
Copy link
Member

Choose a reason for hiding this comment

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

Should the PerformanceMetricsManager perhaps be the one to listen for the app state to change because there'll be multiple batchers for multiple

Alternatively, maybe we would want to send out any pending metrics as soon as you switch away from a server to avoid sending out too many requests to different servers when the app goes to the background

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't want to add more methods to the batcher interface (in this case, would be a flush method). But we could add it, yes.

We could also do as you say, and send the metrics on server switch, but that would have some impact (probably irrelevant though) to the server switch logic. I feel it is better to leave that performance impact to the "send app to the background" interaction.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would agree with both of you.

  1. leave the logic when the app is sent to the background.
  2. move the event listener to the manager and call the sendBatch on each batcher from there.

Important to mention that any timer is paused when the app state changes to background

app/managers/performance_metrics_manager/index.ts Outdated Show resolved Hide resolved
this.ensureBatcher(serverUrl).addToBatch({
metric: metricName,
value: (performanceNow - startTime) / 1000,
timestamp: Math.round(startTime + performanceNowSkew),
Copy link
Member

Choose a reason for hiding this comment

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

Is performance.timeOrigin not available for use here? I remember you mentioned that the performance API is only partially implemented on RN, but I don't know if that one is included

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope. No performance origin apparently. Only performance.now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought I did, and dismissed it because it wasn't maintained for a long time, but I see the last release was March, so I may have mixed different libraries in my mind.

I will re-assess whether this library makes sense for what we want to do.

@larkox larkox requested review from hmhealey and enahum May 27, 2024 09:45
@@ -73,6 +74,11 @@ function PostDraft({
setCursorPosition(message.length);
}, [channelId, rootId]);

useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

ok I understand.. now the next question is why do we consider the channel/thread switch complete when the post_draft renders ? out of curiosity my question, as I want to understand how did you come up with the fact that this is the last component being rendered on screen.

Also, if that is the case, is it still true on tablets ?

Comment on lines 67 to 72
if (!team) {
return;
}
if (selected) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

this could be simplified as

Suggested change
if (!team) {
return;
}
if (selected) {
return;
}
if (!team || selected) {
return;
}

this.ensureBatcher(serverUrl).addToBatch({
metric: metricName,
value: (performanceNow - startTime) / 1000,
timestamp: Math.round(startTime + performanceNowSkew),
Copy link
Contributor

Choose a reason for hiding this comment

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

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {getTimeSinceStartup} from 'react-native-startup-time';
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can use https://github.com/oblador/react-native-performance/blob/master/packages/react-native-performance instead of startup-time, it has all you need for perf metrics as well as start end time but also you can have values for when the JS bundle starts and ends loading and a bunch of other metrics for RN.

more importantly it does already support the new RN arch for when we migrate.

version: '0.1.0',
labels: {
platform: Platform.select({ios: 'ios', default: 'android'}),
agent: 'rnapp',
Copy link
Contributor

Choose a reason for hiding this comment

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

not for this PR, but something we should consider is to come up with a way to identify the type of device (High end, Mid, low end) as that will give us more insight with the collected numbers.

};
}

private onAppStateChange(appState: AppStateStatus) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would agree with both of you.

  1. leave the logic when the app is sent to the background.
  2. move the event listener to the manager and call the sendBatch on each batcher from there.

Important to mention that any timer is paused when the app state changes to background

package.json Outdated
@@ -87,6 +87,7 @@
"react-native-section-list-get-item-layout": "2.2.3",
"react-native-shadow-2": "7.0.8",
"react-native-share": "10.1.0",
"react-native-startup-time": "2.1.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

consider switching the library as suggested

types/api/config.d.ts Outdated Show resolved Hide resolved
@larkox larkox requested a review from enahum May 30, 2024 14:41
@enahum
Copy link
Contributor

enahum commented May 30, 2024

Hey @larkox you sure this is ok? There seems to be a lot of unrelated changes

@larkox
Copy link
Contributor Author

larkox commented May 30, 2024

@enahum A bad merge for some reason... will fix it.

@larkox larkox force-pushed the registerPerformanceMetrics branch from f1bb84c to a2f771f Compare May 30, 2024 15:47
@larkox
Copy link
Contributor Author

larkox commented May 30, 2024

@enahum Bad merge fixed


PerformanceMetricsManager.startMetric('mobile_team_switch');
handleTeamChange(serverUrl, team.id);
}, [selected]);
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't this callback also need to depend on the server URL and team ID?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes ot depends on the team and the team.id but the serverUrl can be omitted as I believe the component is remounted when switching servers

}

private onAppStateChange(appState: AppStateStatus) {
const isAppStateActive = appState === 'active';
Copy link
Member

Choose a reason for hiding this comment

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

Similar to my comments on your PR about the websocket, I don't think we actually need to store the previous value of this if this is only called when the app state changes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are several "non active states", and this is executed when the state become non active. Calling several times forceSend shouldn't have any effect, but still, there is no need to do so. So I think there is not a big issue to store the previous value, to only call this the first time we go to an "inactive state".

this.ensureBatcher(serverUrl).addToBatch({
metric: 'mobile_load',
value: measure.duration,
timestamp: Date.now(),
Copy link
Member

Choose a reason for hiding this comment

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

Now that we have that library, could thse use performance.timeOrigin + measure.startTime?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I got many issues to make this sum up. This is the values I get from the library (on a real android device):

{"dateNow": 1718095572718, "dur": 401.2168229999952, "origin": 553634.5101, "startTime": 623363.507261, "sum": 1176998.017361, "timestamp": 1718095642446.9973}

The timestamp one is calculated as stated here: https://github.com/oblador/react-native-performance/blob/master/packages/react-native-performance/README.md

So... the only value that makes sense to use here is Date.now. The timestamp is also not so important, since it is only used for bucketing, and we are not going so fine grain down to seconds.

If you have any other idea, I am happy to try 😄

Copy link
Member

Choose a reason for hiding this comment

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

Huh, I feel like that doesn't match how the browser API works, but if that's how they say to do it, that's fine by me

@marianunez marianunez added this to the v2.18.0 milestone Jun 6, 2024
@amyblais amyblais added this to the v2.18.0 milestone Jun 6, 2024
@@ -60,6 +61,7 @@ type PostProps = {
post: PostModel;
rootId?: string;
previousPost?: PostModel;
nextPost?: PostModel;
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps you can just pass the boolean isLastPost instead of the entire model?


PerformanceMetricsManager.startMetric('mobile_team_switch');
handleTeamChange(serverUrl, team.id);
}, [selected]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes ot depends on the team and the team.id but the serverUrl can be omitted as I believe the component is remounted when switching servers

this.ensureBatcher(serverUrl).addToBatch({
metric: 'mobile_load',
value: measure.duration,
timestamp: Date.now(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree

package.json Outdated
@@ -199,6 +200,9 @@
"updatesnapshot": "jest --updateSnapshot"
},
"overrides": {
"react-native-startup-time": {
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this

test/test_helper.ts Show resolved Hide resolved
@amyblais amyblais added CherryPick/Approved Meant for the quality or patch release tracked in the milestone Docs/Needed Requires documentation labels Jun 10, 2024
@larkox larkox requested review from hmhealey and enahum June 11, 2024 09:46
@larkox larkox removed the Do Not Merge Should not be merged until this label is removed label Jun 11, 2024
@larkox larkox added 3: QA Review Requires review by a QA tester and removed 2: Dev Review Requires review by a core commiter labels Jun 11, 2024
@larkox
Copy link
Contributor Author

larkox commented Jun 11, 2024

@yasserfaraazkhan For now, the test for this is just a quick smoke test that nothing is broken. Specially on load, channel switch and team switch.

@yasserfaraazkhan yasserfaraazkhan added QA Deferred Agreement with QA that these changes will be tested post-merge Build App for Android Build the mobile app for Android and removed 3: QA Review Requires review by a QA tester labels Jun 11, 2024
@yasserfaraazkhan
Copy link
Contributor

not able to build the app locally on windows machine. Will test it in release build.

@larkox larkox added 4: Reviews Complete All reviewers have approved the pull request and removed Build App for Android Build the mobile app for Android labels Jun 12, 2024
@larkox larkox merged commit 5f01f9e into mattermost:main Jun 12, 2024
31 of 32 checks passed
@mattermost-build
Copy link
Contributor

Cherry pick is scheduled.

mattermost-build pushed a commit that referenced this pull request Jun 12, 2024
* Add performance metrics to the app

* add batcher and improve handling

* Add tests

* Fix test

* Address feedback

* Address feedback

* Address feedback

* update podfile

(cherry picked from commit 5f01f9e)
@mattermost-build mattermost-build added CherryPick/Done Successfully cherry-picked to the quality or patch release tracked in the milestone and removed CherryPick/Approved Meant for the quality or patch release tracked in the milestone labels Jun 12, 2024
larkox added a commit that referenced this pull request Jun 12, 2024
* Add performance metrics to the app

* add batcher and improve handling

* Add tests

* Fix test

* Address feedback

* Address feedback

* Address feedback

* update podfile

(cherry picked from commit 5f01f9e)

Co-authored-by: Daniel Espino García <[email protected]>
@cwarnermm cwarnermm added Docs/Not Needed Does not require documentation and removed Docs/Needed Requires documentation labels Jul 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4: Reviews Complete All reviewers have approved the pull request CherryPick/Done Successfully cherry-picked to the quality or patch release tracked in the milestone Docs/Not Needed Does not require documentation QA Deferred Agreement with QA that these changes will be tested post-merge release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants