@react-native-firebase/perf 16.6.0 → 16.7.0

Sign up to get free protection for your applications and to get access to all the features.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [16.7.0](https://github.com/invertase/react-native-firebase/compare/v16.6.0...v16.7.0) (2023-01-28)
7
+
8
+ ### Features
9
+
10
+ - **perf:** add custom screen rendering traces for android ([#6588](https://github.com/invertase/react-native-firebase/issues/6588)) ([9f2498d](https://github.com/invertase/react-native-firebase/commit/9f2498d29ee3780cba5a7a69fde8f7c370ad723b))
11
+
6
12
  ## [16.6.0](https://github.com/invertase/react-native-firebase/compare/v16.5.2...v16.6.0) (2023-01-27)
7
13
 
8
14
  **Note:** Version bump only for package @react-native-firebase/perf
@@ -0,0 +1,207 @@
1
+ package io.invertase.firebase.perf;
2
+
3
+ /**
4
+ * Copyright 2021 Google Inc. All Rights Reserved.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import android.app.Activity;
20
+ import android.os.Build;
21
+ import android.util.Log;
22
+ import android.util.SparseIntArray;
23
+ import android.view.WindowManager;
24
+
25
+ import androidx.core.app.FrameMetricsAggregator;
26
+
27
+ import com.google.firebase.perf.FirebasePerformance;
28
+ import com.google.firebase.perf.metrics.Trace;
29
+ import com.google.firebase.perf.util.Constants;
30
+
31
+ /**
32
+ * Utility class to capture Screen rendering information (Slow/Frozen frames) for the
33
+ * {@code Activity} passed to the constructor {@link io.invertase.firebase.perf.ScreenTrace#ScreenTrace(Activity, String)}.
34
+ * <p>
35
+ * Learn more at https://firebase.google.com/docs/perf-mon/screen-traces?platform=android.
36
+ * <p>
37
+ * A slow screen rendering often leads to a UI Jank which creates a bad user experience. Below are
38
+ * some tips and references to understand and fix common UI Jank issues:
39
+ * - https://developer.android.com/topic/performance/vitals/render.html#fixing_jank
40
+ * - https://youtu.be/CaMTIgxCSqU (Why 60fps?)
41
+ * - https://youtu.be/HXQhu6qfTVU (Rendering Performance)
42
+ * - https://youtu.be/1iaHxmfZGGc (Understanding VSYNC)
43
+ * - https://www.youtube.com/playlist?list=PLOU2XLYxmsIKEOXh5TwZEv89aofHzNCiu (Android Performance Patterns)
44
+ * <p>
45
+ * References:
46
+ * - Fireperf Source Code
47
+ */
48
+ public class ScreenTrace {
49
+
50
+ private static final String TAG = "RNFirebasePerf";
51
+ private static final String FRAME_METRICS_AGGREGATOR_CLASSNAME =
52
+ "androidx.core.app.FrameMetricsAggregator";
53
+
54
+ private final Activity activity;
55
+ private final String traceName;
56
+
57
+ private final FrameMetricsAggregator frameMetricsAggregator;
58
+ private Trace perfScreenTrace;
59
+
60
+ /**
61
+ * Default constructor for this class.
62
+ *
63
+ * @param activity for which the screen traces should be recorded.
64
+ * @param tag used as an identifier for the name to be used to log screen rendering
65
+ * information (like "MyFancyScreen").
66
+ * @implNote It requires hardware acceleration to be on or it throws.
67
+ */
68
+ public ScreenTrace(Activity activity, String tag) throws IllegalStateException {
69
+ this.activity = activity;
70
+
71
+ // We don't care about adding the activity name to the trace name
72
+ // because RN doesn't care about activities
73
+ this.traceName = tag;
74
+
75
+ boolean isScreenTraceSupported = checkScreenTraceSupport(activity);
76
+
77
+ if (!isScreenTraceSupported) {
78
+ throw new IllegalStateException("Device does not support screen traces. Hardware acceleration must be enabled and Android must not be 8.0 or 8.1.");
79
+ }
80
+
81
+ frameMetricsAggregator = new FrameMetricsAggregator();
82
+ }
83
+
84
+ // region Public APIs
85
+
86
+ /**
87
+ * Starts recording the frame metrics for the screen traces.
88
+ */
89
+ public void recordScreenTrace() {
90
+ Log.d(TAG, "Recording screen trace " + traceName);
91
+
92
+ frameMetricsAggregator.add(activity);
93
+ perfScreenTrace = FirebasePerformance.startTrace(getScreenTraceName());
94
+ }
95
+
96
+ /**
97
+ * Stops recording screen traces and dispatches the trace capturing information on %age of
98
+ * Slow/Frozen frames.
99
+ *
100
+ * Inspired by fireperf source.
101
+ */
102
+ public void sendScreenTrace() {
103
+ if (perfScreenTrace == null) return;
104
+
105
+ int totalFrames = 0;
106
+ int slowFrames = 0;
107
+ int frozenFrames = 0;
108
+
109
+ // Stops recording metrics for this Activity and returns the currently-collected metrics
110
+ SparseIntArray[] arr = frameMetricsAggregator.reset();
111
+
112
+ if (arr != null) {
113
+ SparseIntArray frameTimes = arr[FrameMetricsAggregator.TOTAL_INDEX];
114
+
115
+ if (frameTimes != null) {
116
+ for (int i = 0; i < frameTimes.size(); i++) {
117
+ int frameTime = frameTimes.keyAt(i);
118
+ int numFrames = frameTimes.valueAt(i);
119
+
120
+ totalFrames += numFrames;
121
+
122
+ if (frameTime > Constants.FROZEN_FRAME_TIME) {
123
+ // Frozen frames mean the app appear frozen. The recommended thresholds is 700ms
124
+ frozenFrames += numFrames;
125
+ }
126
+
127
+ if (frameTime > Constants.SLOW_FRAME_TIME) {
128
+ // Slow frames are anything above 16ms (i.e. 60 frames/second)
129
+ slowFrames += numFrames;
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+ // Only incrementMetric if corresponding metric is non-zero.
136
+ if (totalFrames > 0) {
137
+ perfScreenTrace.putMetric(Constants.CounterNames.FRAMES_TOTAL.toString(), totalFrames);
138
+ }
139
+ if (slowFrames > 0) {
140
+ perfScreenTrace.putMetric(Constants.CounterNames.FRAMES_SLOW.toString(), slowFrames);
141
+ }
142
+ if (frozenFrames > 0) {
143
+ perfScreenTrace.putMetric(Constants.CounterNames.FRAMES_FROZEN.toString(), frozenFrames);
144
+ }
145
+
146
+ Log.d(TAG, new StringBuilder()
147
+ .append("sendScreenTrace ").append(traceName)
148
+ .append(", name: ").append(getScreenTraceName())
149
+ .append(", total_frames: ").append(totalFrames)
150
+ .append(", slow_frames: ").append(slowFrames)
151
+ .append(", frozen_frames: ").append(frozenFrames).toString());
152
+
153
+ // Stop and record trace
154
+ perfScreenTrace.stop();
155
+ }
156
+
157
+ // endregion
158
+
159
+ // region Helper Functions
160
+
161
+ private static boolean checkScreenTraceSupport(Activity activity) {
162
+ boolean isValidSDKVersion = checkSDKVersion();
163
+ boolean hasFrameMetricsAggregatorClass = checkFrameMetricsAggregatorClass();
164
+ boolean isActivityHardwareAccelerated = activity.getWindow() != null
165
+ && ((activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0);
166
+
167
+
168
+ boolean supported = isValidSDKVersion && hasFrameMetricsAggregatorClass && isActivityHardwareAccelerated;
169
+
170
+ Log.d(TAG, new StringBuilder()
171
+ .append("isValidSDKVersion: ").append(isValidSDKVersion)
172
+ .append("isScreenTraceSupported(").append(activity).append("): ").append(supported)
173
+ .append(" [hasFrameMetricsAggregatorClass: ").append(hasFrameMetricsAggregatorClass)
174
+ .append(", isActivityHardwareAccelerated: ").append(isActivityHardwareAccelerated).append("]").toString());
175
+
176
+ return supported;
177
+ }
178
+
179
+ private static boolean checkSDKVersion() {
180
+ if (Build.VERSION.SDK_INT == 26 || Build.VERSION.SDK_INT == 27) {
181
+ return false;
182
+ }
183
+
184
+ return true;
185
+ }
186
+
187
+ /**
188
+ * Inspired by fireperf source.
189
+ */
190
+ private static boolean checkFrameMetricsAggregatorClass() {
191
+ try {
192
+ Class<?> initializerClass = Class.forName(FRAME_METRICS_AGGREGATOR_CLASSNAME);
193
+ return true;
194
+ } catch (ClassNotFoundException e) {
195
+ return false;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Inspired by fireperf source.
201
+ */
202
+ private String getScreenTraceName() {
203
+ return Constants.SCREEN_TRACE_PREFIX + traceName;
204
+ }
205
+
206
+ // endregion
207
+ }
@@ -17,6 +17,7 @@ package io.invertase.firebase.perf;
17
17
  *
18
18
  */
19
19
 
20
+ import android.app.Activity;
20
21
  import android.content.Context;
21
22
  import android.os.Bundle;
22
23
  import android.util.SparseArray;
@@ -33,6 +34,7 @@ import java.util.Set;
33
34
 
34
35
  public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
35
36
  private static SparseArray<Trace> traces = new SparseArray<>();
37
+ private static SparseArray<ScreenTrace> screenTraces = new SparseArray<>();
36
38
  private static SparseArray<HttpMetric> httpMetrics = new SparseArray<>();
37
39
 
38
40
  UniversalFirebasePerfModule(Context context, String serviceName) {
@@ -44,6 +46,7 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
44
46
  super.onTearDown();
45
47
  traces.clear();
46
48
  httpMetrics.clear();
49
+ screenTraces.clear();
47
50
  }
48
51
 
49
52
  @Override
@@ -100,6 +103,28 @@ public class UniversalFirebasePerfModule extends UniversalFirebaseModule {
100
103
  });
101
104
  }
102
105
 
106
+ Task<Void> startScreenTrace(Activity activity, int id, String identifier) {
107
+ return Tasks.call(
108
+ () -> {
109
+ ScreenTrace screenTrace = new ScreenTrace(activity, identifier);
110
+ screenTrace.recordScreenTrace();
111
+ screenTraces.put(id, screenTrace);
112
+
113
+ return null;
114
+ });
115
+ }
116
+
117
+ Task<Void> stopScreenTrace(int id) {
118
+ return Tasks.call(
119
+ () -> {
120
+ ScreenTrace trace = screenTraces.get(id);
121
+ trace.sendScreenTrace();
122
+ screenTraces.remove(id);
123
+
124
+ return null;
125
+ });
126
+ }
127
+
103
128
  Task<Void> startHttpMetric(int id, String url, String httpMethod) {
104
129
  return Tasks.call(
105
130
  () -> {
@@ -17,6 +17,7 @@ package io.invertase.firebase.perf;
17
17
  *
18
18
  */
19
19
 
20
+ import android.app.Activity;
20
21
  import com.facebook.react.bridge.Arguments;
21
22
  import com.facebook.react.bridge.Promise;
22
23
  import com.facebook.react.bridge.ReactApplicationContext;
@@ -85,6 +86,42 @@ public class ReactNativeFirebasePerfModule extends ReactNativeFirebaseModule {
85
86
  });
86
87
  }
87
88
 
89
+ @ReactMethod
90
+ public void startScreenTrace(int id, String identifier, Promise promise) {
91
+ Activity currentActivity = getCurrentActivity();
92
+
93
+ // protect against NPEs
94
+ if (currentActivity == null) {
95
+ promise.resolve(null);
96
+ return;
97
+ }
98
+
99
+ module
100
+ .startScreenTrace(currentActivity, id, identifier)
101
+ .addOnCompleteListener(
102
+ task -> {
103
+ if (task.isSuccessful()) {
104
+ promise.resolve(task.getResult());
105
+ } else {
106
+ rejectPromiseWithExceptionMap(promise, task.getException());
107
+ }
108
+ });
109
+ }
110
+
111
+ @ReactMethod
112
+ public void stopScreenTrace(int id, Promise promise) {
113
+ module
114
+ .stopScreenTrace(id)
115
+ .addOnCompleteListener(
116
+ task -> {
117
+ if (task.isSuccessful()) {
118
+ promise.resolve(task.getResult());
119
+ } else {
120
+ rejectPromiseWithExceptionMap(promise, task.getException());
121
+ }
122
+ });
123
+ }
124
+
88
125
  @ReactMethod
89
126
  public void startHttpMetric(int id, String url, String httpMethod, Promise promise) {
90
127
  module
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ import { isIOS } from '@react-native-firebase/app/lib/common';
19
+
20
+ let id = 0;
21
+
22
+ export default class ScreenTrace {
23
+ constructor(native, identifier) {
24
+ this.native = native;
25
+ this._identifier = identifier;
26
+ this._id = id++;
27
+ this._started = false;
28
+ this._stopped = false;
29
+ }
30
+
31
+ start() {
32
+ if (isIOS) {
33
+ return Promise.reject(new Error('Custom screentraces are currently not supported on iOS.'));
34
+ }
35
+ if (this._started) {
36
+ return Promise.resolve(null);
37
+ }
38
+ this._started = true;
39
+
40
+ return this.native.startScreenTrace(this._id, this._identifier);
41
+ }
42
+
43
+ stop() {
44
+ if (!this._started || this._stopped) {
45
+ return Promise.resolve(null);
46
+ }
47
+ this._stopped = true;
48
+
49
+ return this.native.stopScreenTrace(this._id);
50
+ }
51
+ }
package/lib/index.d.ts CHANGED
@@ -211,6 +211,48 @@ export namespace FirebasePerformanceTypes {
211
211
  stop(): Promise<null>;
212
212
  }
213
213
 
214
+ /**
215
+ * ScreenTrace allows you to record a custom screen rendering trace of slow and frozen frames.
216
+ * Throws on constructor if hardware acceleration is off or if Android is 9.0 or 9.1.
217
+ *
218
+ * @platform android Android !== 9.0.0 && Adnroid !== 9.1.0
219
+ */
220
+ export class ScreenTrace {
221
+ /**
222
+ * Starts a new screen trace. Does nothing if already started.
223
+ *
224
+ * #### Example
225
+ *
226
+ * ```js
227
+ * try {
228
+ * const trace = firebase.perf().newScreenTrace('FooScreen');
229
+ * await trace.start();
230
+ * } catch (e) {
231
+ *
232
+ * }
233
+ * ```
234
+ * @platform android Android >= 9.0.0
235
+ */
236
+ start(): Promise<null>;
237
+ /**
238
+ * Stops and sends the screen trace.
239
+ *
240
+ * #### Example
241
+ *
242
+ * ```js
243
+ * try {
244
+ * const trace = firebase.perf().newScreenTrace('FooScreen');
245
+ * await trace.start();
246
+ * await trace.stop();
247
+ * } catch (e) {
248
+ *
249
+ * }
250
+ * ```
251
+ * @platform android Android >= 9.0.0
252
+ */
253
+ stop(): Promise<null>;
254
+ }
255
+
214
256
  /**
215
257
  * Metric used to collect data for network requests/responses. A new instance must be used for every request/response.
216
258
  */
@@ -426,6 +468,45 @@ export namespace FirebasePerformanceTypes {
426
468
  */
427
469
  startTrace(identifier: string): Promise<Trace>;
428
470
 
471
+ /**
472
+ * Creates a ScreenTrace instance with the given identifier.
473
+ * Throws if hardware acceleration is diabled or if Android is 9.0 or 9.1.
474
+ *
475
+ * #### Example
476
+ *
477
+ * ```js
478
+ * try {
479
+ * const trace = firebase.perf().newScreenTrace('FooScreen');
480
+ * await trace.start();
481
+ * } catch (e) {
482
+ *
483
+ * }
484
+ * ```
485
+ *
486
+ * @param identifier Name of the trace, no leading or trailing whitespace allowed, no leading underscore '_' character allowed, max length is 100.
487
+ */
488
+ newScreenTrace(identifier: string): ScreenTrace;
489
+
490
+ /**
491
+ * Creates a ScreenTrace instance with the given identifier and immediately starts it.
492
+ * Throws if hardware acceleration is diabled or if Android is 9.0 or 9.1.
493
+ *
494
+ * #### Example
495
+ *
496
+ * ```js
497
+ * try {
498
+ * const trace = await firebase.perf().startScreenTrace('FooScreen');
499
+ * await trace.stop();
500
+ * } catch (e) {
501
+ *
502
+ * }
503
+ * ```
504
+ * @platform android Android !== 9.0.0 && Android !== 9.1.0
505
+ *
506
+ * @param identifier Name of the screen
507
+ */
508
+ startScreenTrace(identifier: string): Promise<ScreenTrace>;
509
+
429
510
  /**
430
511
  * Creates a HttpMetric instance for collecting network performance data for a single request/response
431
512
  *
package/lib/index.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  } from '@react-native-firebase/app/lib/internal';
24
24
  import HttpMetric from './HttpMetric';
25
25
  import Trace from './Trace';
26
+ import ScreenTrace from './ScreenTrace';
26
27
  import version from './version';
27
28
 
28
29
  const statics = {};
@@ -80,6 +81,21 @@ class FirebasePerfModule extends FirebaseModule {
80
81
  return trace.start().then(() => trace);
81
82
  }
82
83
 
84
+ newScreenTrace(identifier) {
85
+ if (!isString(identifier) || identifier.length > 100) {
86
+ throw new Error(
87
+ "firebase.perf().newScreenTrace(*) 'identifier' must be a string with a maximum length of 100 characters.",
88
+ );
89
+ }
90
+
91
+ return new ScreenTrace(this.native, identifier);
92
+ }
93
+
94
+ startScreenTrace(identifier) {
95
+ const screenTrace = this.newScreenTrace(identifier);
96
+ return screenTrace.start().then(() => screenTrace);
97
+ }
98
+
83
99
  newHttpMetric(url, httpMethod) {
84
100
  if (!isString(url)) {
85
101
  throw new Error("firebase.perf().newHttpMetric(*, _) 'url' must be a string.");
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '16.6.0';
2
+ module.exports = '16.7.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/perf",
3
- "version": "16.6.0",
3
+ "version": "16.7.0",
4
4
  "author": "Invertase <oss@invertase.io> (http://invertase.io)",
5
5
  "description": "React Native Firebase - React Native Firebase provides native integration with Performance Monitoring to gain insight into key performance characteristics within your React Native application.",
6
6
  "main": "lib/index.js",
@@ -29,7 +29,7 @@
29
29
  "performance monitoring"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@react-native-firebase/app": "16.6.0"
32
+ "@react-native-firebase/app": "16.7.0"
33
33
  },
34
34
  "dependencies": {
35
35
  "@expo/config-plugins": "^5.0.4"
@@ -37,5 +37,5 @@
37
37
  "publishConfig": {
38
38
  "access": "public"
39
39
  },
40
- "gitHead": "6663dc24dc5697190b930aa510085a681fe81203"
40
+ "gitHead": "a94f371da16f9bf873c18b09541cbeb19960978d"
41
41
  }