Implement nativePerformanceNow to improve Profiler API results (#27885)

Summary:
When experimenting with React Profiler API (https://reactjs.org/docs/profiler.html), I noticed that durations are integers without a debugger, but they are doubles with higher precision when debugger is attached. After digging into React Profiler code, I found out that it's using `performance.now()` to accumulate execution times of individual units of work. Since this method does not exist in React Native, it falls back to Javascript `Date`, leading to imprecise results.

This PR introduces `global.nativePerformanceNow` function which returns precise native time, and a very basic `performance` polyfill with `now` function.

This will greatly improve React Profiler API results, which is essential for profiling and benchmark tools.

Solves https://github.com/facebook/react-native/issues/27274

## Changelog

[General] [Added] - Implement `nativePerformanceNow` and `performance.now()`
Pull Request resolved: https://github.com/facebook/react-native/pull/27885

Test Plan:
```
const initialTime = global.performance.now();
setTimeout(() => {
  const newTime = global.performance.now();
  console.warn('duration', newTime - initialTime);
}, 1000);
```

### Android + Hermes

![Screenshot_1580198068](https://user-images.githubusercontent.com/13116854/73245757-af0d6c80-41b5-11ea-8130-dde14ebd41a3.png)

### Android + JSC

![Screenshot_1580199089](https://user-images.githubusercontent.com/13116854/73246157-92256900-41b6-11ea-87a6-ac222383200c.png)

### iOS

![Simulator Screen Shot - iPhone 8 - 2020-01-28 at 10 06 49](https://user-images.githubusercontent.com/13116854/73245871-f136ae00-41b5-11ea-9e31-b1eff5717e62.png)

Reviewed By: ejanzer

Differential Revision: D19888289

Pulled By: rickhanlonii

fbshipit-source-id: ab8152382da9aee9b4b3c76f096e45d40f55da6c
This commit is contained in:
Emilis Baliukonis 2020-03-31 10:21:19 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 367a573ccc
Коммит 232517a574
10 изменённых файлов: 123 добавлений и 3 удалений

Просмотреть файл

@ -29,6 +29,7 @@
const start = Date.now();
require('./setUpGlobals');
require('./setUpPerformance');
require('./setUpSystrace');
require('./setUpErrorHandling');
require('./polyfillPromise');

Просмотреть файл

@ -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();
};
}

Просмотреть файл

@ -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;
@ -108,8 +122,8 @@ const Systrace = {
global.nativeTraceEndLegacy(TRACE_TAG_JS_VM_CALLS);
}
if (_canInstallReactHook) {
if (enabled && global.performance === undefined) {
global.performance = userTimingPolyfill;
if (enabled) {
installPerformanceHooks(userTimingPolyfill);
}
}
}

Просмотреть файл

@ -24,6 +24,13 @@ std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
_RCTLogJavaScriptInternal(static_cast<RCTLogLevel>(logLevel), [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);
// Wrap over the original runtimeInstaller
if (runtimeInstaller) {
runtimeInstaller(runtime);

Просмотреть файл

@ -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>
@ -48,6 +49,10 @@ 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

Просмотреть файл

@ -11,6 +11,7 @@
#include <react/jni/JReactMarker.h>
#include <react/jni/JSLogging.h>
#include <react/jni/JavaScriptExecutorHolder.h>
#include <react/jni/NativeTime.h>
#include <react/jni/ReadableNativeMap.h>
#include <memory>
@ -30,6 +31,10 @@ 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(),

Просмотреть файл

@ -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.
*/
#include "NativeTime.h"
#include <chrono>
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

Просмотреть файл

@ -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

Просмотреть файл

@ -483,5 +483,20 @@ 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

Просмотреть файл

@ -135,5 +135,10 @@ 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