diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 5ea001faaa..9be5277daf 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -108,8 +108,8 @@ public class DevServerHelper { public interface PackagerCommandListener { void onPackagerReloadCommand(); - void onCaptureHeapCommand(@Nullable final Responder responder); - void onPokeSamplingProfilerCommand(@Nullable final Responder responder); + void onCaptureHeapCommand(final Responder responder); + void onPokeSamplingProfilerCommand(final Responder responder); } public interface SymbolicationListener { diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 028f52411a..b65cc8cd81 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -45,6 +45,7 @@ import com.facebook.react.devsupport.interfaces.StackFrame; import com.facebook.react.modules.debug.interfaces.DeveloperSettings; import com.facebook.react.packagerconnection.JSPackagerClient; import com.facebook.react.packagerconnection.Responder; +import com.facebook.react.packagerconnection.SamplingProfilerPackagerMethod; import java.io.File; import java.io.IOException; @@ -433,7 +434,7 @@ public class DevSupportManagerImpl implements new DevOptionHandler() { @Override public void onOptionSelected() { - handlePokeSamplingProfiler(null); + handlePokeSamplingProfiler(); } }); options.put( @@ -690,11 +691,17 @@ public class DevSupportManagerImpl implements } @Override - public void onPokeSamplingProfilerCommand(@Nullable final Responder responder) { + public void onPokeSamplingProfilerCommand(final Responder responder) { UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { - handlePokeSamplingProfiler(responder); + if (mCurrentContext == null) { + responder.error("JSCContext is missing, unable to profile"); + return; + } + SamplingProfilerPackagerMethod method = + new SamplingProfilerPackagerMethod(mCurrentContext.getJavaScriptContext()); + method.onRequest(null, responder); } }); } @@ -719,7 +726,7 @@ public class DevSupportManagerImpl implements }); } - private void handlePokeSamplingProfiler(@Nullable final Responder responder) { + private void handlePokeSamplingProfiler() { try { List pokeResults = JSCSamplingProfiler.poke(60000); for (String result : pokeResults) { @@ -729,16 +736,9 @@ public class DevSupportManagerImpl implements ? "Started JSC Sampling Profiler" : "Stopped JSC Sampling Profiler", Toast.LENGTH_LONG).show(); - if (responder != null) { - // Responder is provided, so there is a client waiting our response - responder.respond(result == null ? "started" : result); - } else if (result != null) { - // The profile was not initiated by external client, so process the - // profile if there is one in the result - new JscProfileTask(getSourceUrl()).executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR, - result); - } + new JscProfileTask(getSourceUrl()).executeOnExecutor( + AsyncTask.THREAD_POOL_EXECUTOR, + result); } } catch (JSCSamplingProfiler.ProfilerException e) { showNewJavaError(e.getMessage(), e); diff --git a/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK b/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK index 25f3213889..0fd14fabaa 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/BUCK @@ -7,12 +7,16 @@ android_library( "PUBLIC", ], deps = [ + react_native_dep("java/com/facebook/jni:jni"), + react_native_dep("java/com/facebook/proguard/annotations:annotations"), react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), + react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"), react_native_dep("third-party/java/infer-annotations:infer-annotations"), react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/okhttp:okhttp3"), react_native_dep("third-party/java/okhttp:okhttp3-ws"), react_native_dep("third-party/java/okio:okio"), react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo-moduleless"), + react_native_target("jni/packagerconnection:jni"), ], ) diff --git a/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/SamplingProfilerPackagerMethod.java b/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/SamplingProfilerPackagerMethod.java new file mode 100644 index 0000000000..a4457d7393 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/SamplingProfilerPackagerMethod.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.packagerconnection; + +import javax.annotation.Nullable; + +import android.os.Looper; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.soloader.SoLoader; + +public class SamplingProfilerPackagerMethod extends RequestOnlyHandler { + static { + SoLoader.loadLibrary("packagerconnectionjnifb"); + } + + final private static class SamplingProfilerJniMethod { + + @DoNotStrip + private final HybridData mHybridData; + + public SamplingProfilerJniMethod(long javaScriptContext) { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + mHybridData = initHybrid(javaScriptContext); + } + + @DoNotStrip + private native void poke(Responder responder); + + @DoNotStrip + private static native HybridData initHybrid(long javaScriptContext); + } + + private SamplingProfilerJniMethod mJniMethod; + + public SamplingProfilerPackagerMethod(long javaScriptContext) { + mJniMethod = new SamplingProfilerJniMethod(javaScriptContext); + } + + @Override + public void onRequest(@Nullable Object params, Responder responder) { + mJniMethod.poke(responder); + } +} diff --git a/ReactAndroid/src/main/jni/packagerconnection/Android.mk b/ReactAndroid/src/main/jni/packagerconnection/Android.mk new file mode 100644 index 0000000000..ba09a7e689 --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/Android.mk @@ -0,0 +1,29 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := packagerconnectionjnifb + +LOCAL_SRC_FILES := \ + JSPackagerClientResponder.h \ + SamplingProfilerPackagerMethod.cpp \ + +LOCAL_C_INCLUDES := $(LOCAL_PATH) +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../ + +LOCAL_CFLAGS += -Wall -Werror -fvisibility=hidden -fexceptions -frtti +CXX11_FLAGS := -std=c++11 +LOCAL_CFLAGS += $(CXX11_FLAGS) +LOCAL_EXPORT_CPPFLAGS := $(CXX11_FLAGS) + +LOCAL_LDLIBS += -landroid +LOCAL_SHARED_LIBRARIES := libfolly_json libfbjni libjsc libglog_init + +include $(BUILD_SHARED_LIBRARY) + +$(call import-module,fb) +$(call import-module,jsc) +$(call import-module,folly) +$(call import-module,fbgloginit) +$(call import-module,jni) +$(call import-module,jscwrapper) diff --git a/ReactAndroid/src/main/jni/packagerconnection/BUCK b/ReactAndroid/src/main/jni/packagerconnection/BUCK new file mode 100644 index 0000000000..1572a253f6 --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/BUCK @@ -0,0 +1,35 @@ +include_defs("//ReactAndroid/DEFS") + +cxx_library( + name = "jni", + srcs = glob(["*.cpp"]), + compiler_flags = [ + "-Wall", + "-Werror", + "-fexceptions", + "-std=c++1y", + "-frtti", + ], + header_namespace = "", + headers = glob( + ["*.h"], + ), + preprocessor_flags = [ + "-DLOG_TAG=\"PackagerConnectionJNI\"", + "-DWITH_FBSYSTRACE=1", + "-DWITH_INSPECTOR=1", + ], + soname = "libpackagerconnectionjnifb.$(ext)", + visibility = [ + "PUBLIC", + ], + xcode_public_headers_symlinks = True, + deps = JSC_DEPS + [ + "//native/fb:fb", + "//native/third-party/android-ndk:android", + "//xplat/folly:molly", + "//xplat/fbgloginit:fbgloginit", + "//xplat/fbsystrace:fbsystrace", + react_native_xplat_target("jschelpers:jschelpers"), + ], +) diff --git a/ReactAndroid/src/main/jni/packagerconnection/JSPackagerClientResponder.cpp b/ReactAndroid/src/main/jni/packagerconnection/JSPackagerClientResponder.cpp new file mode 100644 index 0000000000..786af299cc --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/JSPackagerClientResponder.cpp @@ -0,0 +1,32 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "JSPackagerClientResponder.h" + +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +void JSPackagerClientResponder::respond(alias_ref result) { + static auto method = + javaClassStatic()->getMethod)>("respond"); + method(self(), result); +} + +void JSPackagerClientResponder::respond(const std::string &result) { + respond(LocalString(result).string()); +} + +void JSPackagerClientResponder::error(alias_ref result) { + static auto method = + javaClassStatic()->getMethod)>("error"); + method(self(), result); +} + +void JSPackagerClientResponder::error(const std::string &result) { + error(LocalString(result).string()); +} +} +} diff --git a/ReactAndroid/src/main/jni/packagerconnection/JSPackagerClientResponder.h b/ReactAndroid/src/main/jni/packagerconnection/JSPackagerClientResponder.h new file mode 100644 index 0000000000..243a409c78 --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/JSPackagerClientResponder.h @@ -0,0 +1,22 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include + +namespace facebook { +namespace react { + +class JSPackagerClientResponder + : public jni::JavaClass { +public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/packagerconnection/Responder;"; + + void respond(jni::alias_ref result); + void respond(const std::string& result); + + void error(jni::alias_ref result); + void error(const std::string& result); +}; + +} +} diff --git a/ReactAndroid/src/main/jni/packagerconnection/OnLoad.cpp b/ReactAndroid/src/main/jni/packagerconnection/OnLoad.cpp new file mode 100644 index 0000000000..d9b6b8cad4 --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/OnLoad.cpp @@ -0,0 +1,17 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include +#include + +#include "SamplingProfilerJniMethod.h" + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { + return initialize(vm, [] { SamplingProfilerJniMethod::registerNatives(); }); +} +} +} diff --git a/ReactAndroid/src/main/jni/packagerconnection/SamplingProfilerJniMethod.cpp b/ReactAndroid/src/main/jni/packagerconnection/SamplingProfilerJniMethod.cpp new file mode 100644 index 0000000000..3d4eb28ac4 --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/SamplingProfilerJniMethod.cpp @@ -0,0 +1,50 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "SamplingProfilerJniMethod.h" + +#include +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +/* static */ jni::local_ref +SamplingProfilerJniMethod::initHybrid(jni::alias_ref, + jlong javaScriptContext) { + return makeCxxInstance(javaScriptContext); +} + +/* static */ void SamplingProfilerJniMethod::registerNatives() { + registerHybrid( + {makeNativeMethod("initHybrid", SamplingProfilerJniMethod::initHybrid), + makeNativeMethod("poke", SamplingProfilerJniMethod::poke)}); +} + +SamplingProfilerJniMethod::SamplingProfilerJniMethod(jlong javaScriptContext) { + context_ = reinterpret_cast(javaScriptContext); +} + +void SamplingProfilerJniMethod::poke( + jni::alias_ref responder) { + if (!JSC_JSSamplingProfilerEnabled(context_)) { + responder->error("The JSSamplingProfiler is disabled. See this " + "https://fburl.com/u4lw7xeq for some help"); + return; + } + + JSValueRef jsResult = JSC_JSPokeSamplingProfiler(context_); + if (JSC_JSValueGetType(context_, jsResult) == kJSTypeNull) { + responder->respond("started"); + } else { + JSStringRef resultStrRef = JSValueToStringCopy(context_, jsResult, nullptr); + size_t length = JSStringGetLength(resultStrRef); + char buffer[length + 1]; + JSStringGetUTF8CString(resultStrRef, buffer, length + 1); + JSStringRelease(resultStrRef); + responder->respond(buffer); + } +} +} +} diff --git a/ReactAndroid/src/main/jni/packagerconnection/SamplingProfilerJniMethod.h b/ReactAndroid/src/main/jni/packagerconnection/SamplingProfilerJniMethod.h new file mode 100644 index 0000000000..070b271241 --- /dev/null +++ b/ReactAndroid/src/main/jni/packagerconnection/SamplingProfilerJniMethod.h @@ -0,0 +1,35 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include +#include +#include +#include + +#include "JSPackagerClientResponder.h" + +namespace facebook { +namespace react { + +class SamplingProfilerJniMethod + : public jni::HybridClass { +public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/packagerconnection/" + "SamplingProfilerPackagerMethod$SamplingProfilerJniMethod;"; + + static jni::local_ref initHybrid(jni::alias_ref jthis, + jlong javaScriptContext); + + static void registerNatives(); + +private: + friend HybridBase; + + explicit SamplingProfilerJniMethod(jlong javaScriptContext); + + void poke(jni::alias_ref responder); + + JSGlobalContextRef context_; +}; +} +}