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

Implement nativePerformanceNow to improve Profiler API results #27885

Closed
wants to merge 9 commits into from
1 change: 1 addition & 0 deletions Libraries/Core/InitializeCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
const start = Date.now();

require('./setUpGlobals');
require('./setUpPerformance');
require('./setUpSystrace');
require('./setUpErrorHandling');
require('./polyfillPromise');
Expand Down
26 changes: 26 additions & 0 deletions Libraries/Core/setUpPerformance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) Facebook, Inc. and its 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-local
* @format
*/

'use strict';

if (!global.performance) {
global.performance = {};
}

/**
* Returns a double, measured in milliseconds.
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/now
*/
if (typeof global.performance.now !== 'function') {
global.performance.now = function() {
const performanceNow = global.nativePerformanceNow || Date.now;
return performanceNow();
};
}
22 changes: 17 additions & 5 deletions Libraries/Performance/Systrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,25 @@ const userTimingPolyfill = __DEV__
}
: null;

function installPerformanceHooks(polyfill) {
if (polyfill) {
if (global.performance === undefined) {
global.performance = {};
}

Object.keys(polyfill).forEach(methodName => {
if (typeof global.performance[methodName] !== 'function') {
global.performance[methodName] = polyfill[methodName];
}
});
}
}

const Systrace = {
installReactHook() {
if (_enabled) {
if (__DEV__) {
global.performance = userTimingPolyfill;
installPerformanceHooks(userTimingPolyfill);
}
}
_canInstallReactHook = true;
Expand All @@ -107,10 +121,8 @@ const Systrace = {
global.nativeTraceEndLegacy &&
global.nativeTraceEndLegacy(TRACE_TAG_JS_VM_CALLS);
}
if (_canInstallReactHook) {
if (enabled && global.performance === undefined) {
global.performance = userTimingPolyfill;
}
if (_canInstallReactHook && enabled) {
installPerformanceHooks(userTimingPolyfill);
}
}
_enabled = enabled;
Expand Down
7 changes: 7 additions & 0 deletions React/CxxBridge/JSCExecutorFactory.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
[NSString stringWithUTF8String:message.c_str()]);
};
react::bindNativeLogger(runtime, iosLoggingBinder);

react::PerformanceNow iosPerformanceNowBinder =[]() {
// CACurrentMediaTime() returns the current absolute time, in seconds
return CACurrentMediaTime() * 1000;
};
react::bindNativePerformanceNow(runtime, iosPerformanceNowBinder);
Copy link

Choose a reason for hiding this comment

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

Is there a reason to bind this here (in each executor factory) instead of in somewhere more central like Instance::bindBridge? (I guess that would probably involve changing the API for bindBridge to add another parameter, which would be a breaking change for any other executors that aren't part of RN core). I'm just wondering because it seems like the difference between how we bind nativePerformanceNow is based on the platform we're running on, not the JS VM, so we don't necessarily need this to be in the JS executor factory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seemed like a good place to use without making any breaking changes. Logging binder is there too.


// Wrap over the original runtimeInstaller
if (runtimeInstaller) {
runtimeInstaller(runtime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <react/jni/JReactMarker.h>
#include <react/jni/JSLogging.h>
#include <react/jni/JavaScriptExecutorHolder.h>
#include <react/jni/NativeTime.h>

#include <memory>

Expand Down Expand Up @@ -47,6 +48,11 @@ static void installBindings(jsi::Runtime &runtime) {
static_cast<void (*)(const std::string &, unsigned int)>(
&reactAndroidLoggingHook);
react::bindNativeLogger(runtime, androidLogger);

react::PerformanceNow androidNativePerformanceNow =
static_cast<double (*)()>(
&reactAndroidNativePerformanceNowHook);
react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
}

class HermesExecutorHolder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <react/jni/JSLogging.h>
#include <react/jni/JavaScriptExecutorHolder.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/NativeTime.h>

#include <memory>

Expand All @@ -30,6 +31,11 @@ class JSCExecutorFactory : public JSExecutorFactory {
static_cast<void (*)(const std::string &, unsigned int)>(
&reactAndroidLoggingHook);
react::bindNativeLogger(runtime, androidLogger);

react::PerformanceNow androidNativePerformanceNow =
static_cast<double (*)()>(
&reactAndroidNativePerformanceNowHook);
react::bindNativePerformanceNow(runtime, androidNativePerformanceNow);
};
return std::make_unique<JSIExecutor>(
jsc::makeJSCRuntime(),
Expand Down
24 changes: 24 additions & 0 deletions ReactAndroid/src/main/jni/react/jni/NativeTime.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <chrono>
#include "NativeTime.h"

namespace facebook {
namespace react {

double reactAndroidNativePerformanceNowHook() {
auto time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(time.time_since_epoch()).count();

constexpr double NANOSECONDS_IN_MILLISECOND = 1000000.0;

return duration / NANOSECONDS_IN_MILLISECOND;
}

} // namespace react
} // namespace facebook
16 changes: 16 additions & 0 deletions ReactAndroid/src/main/jni/react/jni/NativeTime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

namespace facebook {
namespace react {

double reactAndroidNativePerformanceNowHook();

} // namespace react
} // namespace facebook
17 changes: 17 additions & 0 deletions ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,5 +417,22 @@ void bindNativeLogger(Runtime &runtime, Logger logger) {
}));
}

void bindNativePerformanceNow(Runtime &runtime, PerformanceNow performanceNow) {
runtime.global().setProperty(
runtime,
"nativePerformanceNow",
Function::createFromHostFunction(
runtime,
PropNameID::forAscii(runtime, "nativePerformanceNow"),
0,
[performanceNow = std::move(performanceNow)](
jsi::Runtime &runtime,
const jsi::Value &,
const jsi::Value *args,
size_t count) {
return Value(performanceNow());
}));
}

} // namespace react
} // namespace facebook
3 changes: 3 additions & 0 deletions ReactCommon/jsiexecutor/jsireact/JSIExecutor.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,8 @@ class JSIExecutor : public JSExecutor {
using Logger =
std::function<void(const std::string &message, unsigned int logLevel)>;
void bindNativeLogger(jsi::Runtime &runtime, Logger logger);

using PerformanceNow = std::function<double()>;
void bindNativePerformanceNow(jsi::Runtime &runtime, PerformanceNow performanceNow);
} // namespace react
} // namespace facebook