Skip to content

Commit

Permalink
Add performance.reactNativeStartupTiming API
Browse files Browse the repository at this point in the history
Summary:
This diff adds the `performance.reactNativeStartupTiming` API to the performance global object for RN. This property does not exist in web, and we are free to make up our own list of properties in the startup metrics to track RN app startup process. In our case, we have the following six properties to begin with (we may extend and add more to this list in the future):

```
- `(start|end)Time`: The time ‘zero’ for the startup timing and the end of app startup. RN has no knowledge of app start time, which will be provided by the platform. The `endTime` will be the time when the first JS bundle finishes executing (Note that RN supports multiple JS bundles, which can be loaded async)
  - `executeJavaScriptBundleEntryPoint(Start|End)`: The time for RN to execute the JS entry point (and finish all sync job)

```

Changelog:
[General][Added] - Add new JS performance API to support getting RN app startup timings

Reviewed By: rshest

Differential Revision: D43326564

fbshipit-source-id: 7b4c7cae70ff64ba1714a1630cd5e183df6c06b0
  • Loading branch information
Xin Chen authored and facebook-github-bot committed Mar 3, 2023
1 parent ba9327d commit c1023c7
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 13 deletions.
28 changes: 28 additions & 0 deletions Libraries/WebPerformance/NativePerformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <memory>

#include <cxxreact/ReactMarker.h>
#include <jsi/instrumentation.h>
#include "NativePerformance.h"
#include "PerformanceEntryReporter.h"
Expand Down Expand Up @@ -54,4 +55,31 @@ std::unordered_map<std::string, double> NativePerformance::getSimpleMemoryInfo(
return heapInfoToJs;
}

ReactNativeStartupTiming NativePerformance::getReactNativeStartupTiming(
jsi::Runtime &rt) {
ReactNativeStartupTiming result = {0, 0, 0, 0};
result.startTime = ReactMarker::getAppStartTime();

auto startupReactMarkers =
ReactMarker::StartupLogger::getInstance().getStartupReactMarkers();
for (const auto &startupReactMarker : startupReactMarkers) {
auto time = startupReactMarker.time;
switch (startupReactMarker.markerId) {
case ReactMarker::ReactMarkerId::RUN_JS_BUNDLE_START:
result.executeJavaScriptBundleEntryPointStart = time;
break;

case ReactMarker::ReactMarkerId::RUN_JS_BUNDLE_STOP:
result.executeJavaScriptBundleEntryPointEnd = time;
result.endTime = time;
break;

default:
break;
}
}

return result;
}

} // namespace facebook::react
20 changes: 20 additions & 0 deletions Libraries/WebPerformance/NativePerformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ class PerformanceEntryReporter;

#pragma mark - Structs

using ReactNativeStartupTiming =
NativePerformanceCxxBaseReactNativeStartupTiming<
int32_t, // Start time of the RN app startup process
int32_t, // End time of the RN app startup process
int32_t, // Start time that RN app execute the JS bundle
int32_t // End time that RN app execute the JS bundle
>;

template <>
struct Bridging<ReactNativeStartupTiming>
: NativePerformanceCxxBaseReactNativeStartupTimingBridging<
int32_t,
int32_t,
int32_t,
int32_t> {};

#pragma mark - implementation

class NativePerformance : public NativePerformanceCxxSpec<NativePerformance>,
Expand Down Expand Up @@ -50,6 +66,10 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance>,
// for heap size information, as double's 2^53 sig bytes is large enough.
std::unordered_map<std::string, double> getSimpleMemoryInfo(jsi::Runtime &rt);

// Collect and return the RN app startup timing information for performance
// tracking.
ReactNativeStartupTiming getReactNativeStartupTiming(jsi::Runtime &rt);

private:
};

Expand Down
8 changes: 8 additions & 0 deletions Libraries/WebPerformance/NativePerformance.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';

export type NativeMemoryInfo = {[key: string]: number};

export type ReactNativeStartupTiming = {|
startTime: number,
endTime: number,
executeJavaScriptBundleEntryPointStart: number,
executeJavaScriptBundleEntryPointEnd: number,
|};

export interface Spec extends TurboModule {
+mark: (name: string, startTime: number, duration: number) => void;
+measure: (
Expand All @@ -25,6 +32,7 @@ export interface Spec extends TurboModule {
endMark?: string,
) => void;
+getSimpleMemoryInfo: () => NativeMemoryInfo;
+getReactNativeStartupTiming: () => ReactNativeStartupTiming;
}

export default (TurboModuleRegistry.get<Spec>('NativePerformanceCxx'): ?Spec);
11 changes: 11 additions & 0 deletions Libraries/WebPerformance/Performance.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
rawToPerformanceEntry,
} from './RawPerformanceEntry';
import {RawPerformanceEntryTypeValues} from './RawPerformanceEntry';
import ReactNativeStartupTiming from './ReactNativeStartupTiming';

type DetailType = mixed;

Expand Down Expand Up @@ -127,6 +128,16 @@ export default class Performance {
return new MemoryInfo();
}

// Startup metrics is not used in web, but only in React Native.
get reactNativeStartupTiming(): ReactNativeStartupTiming {
if (NativePerformance?.getReactNativeStartupTiming) {
return new ReactNativeStartupTiming(
NativePerformance.getReactNativeStartupTiming(),
);
}
return new ReactNativeStartupTiming();
}

mark(
markName: string,
markOptions?: PerformanceMarkOptions,
Expand Down
65 changes: 65 additions & 0 deletions Libraries/WebPerformance/ReactNativeStartupTiming.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/

// flowlint unsafe-getters-setters:off

import type {ReactNativeStartupTiming as ReactNativeStartupTimingType} from './NativePerformance';

// Read-only object with RN startup timing information.
// This is returned by the performance.reactNativeStartup API.
export default class ReactNativeStartupTiming {
// All time information here are in ms. To match web spec,
// the default value for timings are zero if not present.
// See https://www.w3.org/TR/performance-timeline/#performancetiming-interface
_startTime = 0;
_endTime = 0;
_executeJavaScriptBundleEntryPointStart = 0;
_executeJavaScriptBundleEntryPointEnd = 0;

constructor(startUpTiming: ?ReactNativeStartupTimingType) {
if (startUpTiming != null) {
this._startTime = startUpTiming.startTime;
this._endTime = startUpTiming.endTime;
this._executeJavaScriptBundleEntryPointStart =
startUpTiming.executeJavaScriptBundleEntryPointStart;
this._executeJavaScriptBundleEntryPointEnd =
startUpTiming.executeJavaScriptBundleEntryPointEnd;
}
}

/**
* Start time of the RN app startup process. This is provided by the platform by implementing the `ReactMarker.setAppStartTime` API in the native platform code.
*/
get startTime(): number {
return this._startTime;
}

/**
* End time of the RN app startup process. This is equal to `executeJavaScriptBundleEntryPointEnd`.
*/
get endTime(): number {
return this._endTime;
}

/**
* Start time of JS bundle being executed. This indicates the RN JS bundle is loaded and start to be evaluated.
*/
get executeJavaScriptBundleEntryPointStart(): number {
return this._executeJavaScriptBundleEntryPointStart;
}

/**
* End time of JS bundle being executed. This indicates all the synchronous entry point jobs are finished.
*/
get executeJavaScriptBundleEntryPointEnd(): number {
return this._executeJavaScriptBundleEntryPointEnd;
}
}
3 changes: 2 additions & 1 deletion React/CxxBridge/RCTCxxBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ static void mapReactMarkerToPerformanceLogger(
static void registerPerformanceLoggerHooks(RCTPerformanceLogger *performanceLogger)
{
__weak RCTPerformanceLogger *weakPerformanceLogger = performanceLogger;
ReactMarker::logTaggedMarker = [weakPerformanceLogger](const ReactMarker::ReactMarkerId markerId, const char *tag) {
ReactMarker::logTaggedMarkerImpl = [weakPerformanceLogger](
const ReactMarker::ReactMarkerId markerId, const char *tag) {
mapReactMarkerToPerformanceLogger(markerId, weakPerformanceLogger, tag);
};
}
Expand Down
4 changes: 2 additions & 2 deletions ReactAndroid/src/main/jni/react/jni/JReactMarker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ namespace react {
void JReactMarker::setLogPerfMarkerIfNeeded() {
static std::once_flag flag{};
std::call_once(flag, []() {
ReactMarker::logTaggedMarker = JReactMarker::logPerfMarker;
ReactMarker::logTaggedMarkerBridgeless =
ReactMarker::logTaggedMarkerImpl = JReactMarker::logPerfMarker;
ReactMarker::logTaggedMarkerBridgelessImpl =
JReactMarker::logPerfMarkerBridgeless;
});
}
Expand Down
48 changes: 46 additions & 2 deletions ReactCommon/cxxreact/ReactMarker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "ReactMarker.h"
#include <cxxreact/JSExecutor.h>

namespace facebook {
namespace react {
Expand All @@ -16,8 +17,9 @@ namespace ReactMarker {
#pragma clang diagnostic ignored "-Wglobal-constructors"
#endif

LogTaggedMarker logTaggedMarker = nullptr;
LogTaggedMarker logTaggedMarkerBridgeless = nullptr;
LogTaggedMarker logTaggedMarkerImpl = nullptr;
LogTaggedMarker logTaggedMarkerBridgelessImpl = nullptr;
GetAppStartTime getAppStartTimeImpl = nullptr;

#if __clang__
#pragma clang diagnostic pop
Expand All @@ -27,10 +29,52 @@ void logMarker(const ReactMarkerId markerId) {
logTaggedMarker(markerId, nullptr);
}

void logTaggedMarker(const ReactMarkerId markerId, const char *tag) {
StartupLogger::getInstance().logStartupEvent(markerId, tag);
logTaggedMarkerImpl(markerId, tag);
}

void logMarkerBridgeless(const ReactMarkerId markerId) {
logTaggedMarkerBridgeless(markerId, nullptr);
}

void logTaggedMarkerBridgeless(const ReactMarkerId markerId, const char *tag) {
StartupLogger::getInstance().logStartupEvent(markerId, tag);
logTaggedMarkerBridgelessImpl(markerId, tag);
}

double getAppStartTime() {
if (getAppStartTimeImpl == nullptr) {
return 0;
}

return getAppStartTimeImpl();
}

StartupLogger &StartupLogger::getInstance() {
static StartupLogger instance;
return instance;
}

void StartupLogger::logStartupEvent(
const ReactMarkerId markerId,
const char *tag) {
if (startupStopped) {
return;
}

if (markerId == ReactMarkerId::RUN_JS_BUNDLE_START ||
markerId == ReactMarkerId::RUN_JS_BUNDLE_STOP) {
startupReactMarkers.push_back(
{markerId, tag, JSExecutor::performanceNow()});
startupStopped = markerId == ReactMarkerId::RUN_JS_BUNDLE_STOP;
}
}

std::vector<ReactMarkerEvent> StartupLogger::getStartupReactMarkers() {
return startupReactMarkers;
}

} // namespace ReactMarker
} // namespace react
} // namespace facebook
38 changes: 36 additions & 2 deletions ReactCommon/cxxreact/ReactMarker.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#pragma once

#include <vector>

#ifdef __APPLE__
#include <functional>
#endif
Expand Down Expand Up @@ -36,21 +38,53 @@ using LogTaggedMarker =
std::function<void(const ReactMarkerId, const char *tag)>; // Bridge only
using LogTaggedMarkerBridgeless =
std::function<void(const ReactMarkerId, const char *tag)>;
using GetAppStartTime = std::function<double()>;
#else
typedef void (
*LogTaggedMarker)(const ReactMarkerId, const char *tag); // Bridge only
typedef void (*LogTaggedMarkerBridgeless)(const ReactMarkerId, const char *tag);
typedef double (*GetAppStartTime)();
#endif

#ifndef RN_EXPORT
#define RN_EXPORT __attribute__((visibility("default")))
#endif

extern RN_EXPORT LogTaggedMarker logTaggedMarker; // Bridge only
extern RN_EXPORT LogTaggedMarker logTaggedMarkerBridgeless;
extern RN_EXPORT LogTaggedMarker logTaggedMarkerImpl; // Bridge only
extern RN_EXPORT LogTaggedMarker logTaggedMarkerBridgelessImpl;
extern RN_EXPORT GetAppStartTime getAppStartTimeImpl;

extern RN_EXPORT void logMarker(const ReactMarkerId markerId); // Bridge only
extern RN_EXPORT void logTaggedMarker(
const ReactMarkerId markerId,
const char *tag); // Bridge only
extern RN_EXPORT void logMarkerBridgeless(const ReactMarkerId markerId);
extern RN_EXPORT void logTaggedMarkerBridgeless(
const ReactMarkerId markerId,
const char *tag);
extern RN_EXPORT double getAppStartTime();

struct ReactMarkerEvent {
const ReactMarkerId markerId;
const char *tag;
double time;
};

class StartupLogger {
public:
static StartupLogger &getInstance();

void logStartupEvent(const ReactMarkerId markerId, const char *tag);
std::vector<ReactMarkerEvent> getStartupReactMarkers();

private:
StartupLogger() = default;
StartupLogger(const StartupLogger &) = delete;
StartupLogger &operator=(const StartupLogger &) = delete;

bool startupStopped;
std::vector<ReactMarkerEvent> startupReactMarkers;
};

} // namespace ReactMarker
} // namespace react
Expand Down
4 changes: 2 additions & 2 deletions ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ void JSIExecutor::initializeRuntime() {
if (runtimeInstaller_) {
runtimeInstaller_(*runtime_);
}
bool hasLogger(ReactMarker::logTaggedMarker);
bool hasLogger(ReactMarker::logTaggedMarkerImpl);
if (hasLogger) {
ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
}
Expand All @@ -148,7 +148,7 @@ void JSIExecutor::loadBundle(
std::string sourceURL) {
SystraceSection s("JSIExecutor::loadBundle");

bool hasLogger(ReactMarker::logTaggedMarker);
bool hasLogger(ReactMarker::logTaggedMarkerImpl);
std::string scriptName = simpleBasename(sourceURL);
if (hasLogger) {
ReactMarker::logTaggedMarker(
Expand Down
2 changes: 1 addition & 1 deletion ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ void JSINativeModules::reset() {
std::optional<Object> JSINativeModules::createModule(
Runtime &rt,
const std::string &name) {
bool hasLogger(ReactMarker::logTaggedMarker);
bool hasLogger(ReactMarker::logTaggedMarkerImpl);
if (hasLogger) {
ReactMarker::logTaggedMarker(
ReactMarker::NATIVE_MODULE_SETUP_START, name.c_str());
Expand Down
Loading

0 comments on commit c1023c7

Please sign in to comment.