diff --git a/JSCLegacyProfiler/JSCLegacyProfiler.h b/JSCLegacyProfiler/JSCLegacyProfiler.h new file mode 100644 index 000000000..826e39f21 --- /dev/null +++ b/JSCLegacyProfiler/JSCLegacyProfiler.h @@ -0,0 +1,25 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#import "JSContextRef.h" + +extern "C" { + +JSValueRef nativeProfilerStart( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception); + +JSValueRef nativeProfilerEnd( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception); + +} diff --git a/JSCLegacyProfiler/JSCLegacyProfiler.mm b/JSCLegacyProfiler/JSCLegacyProfiler.mm new file mode 100644 index 000000000..218c5e55d --- /dev/null +++ b/JSCLegacyProfiler/JSCLegacyProfiler.mm @@ -0,0 +1,161 @@ +//#include "config.h" + +#include "JSCLegacyProfiler.h" + +#include "APICast.h" +#include "LegacyProfiler.h" +#include "OpaqueJSString.h" +#include "JSProfilerPrivate.h" +#include "JSStringRef.h" + +#include + +#define GEN_AND_CHECK(expr) \ + do { \ + yajl_gen_status GEN_AND_CHECK_status = (expr); \ + if (GEN_AND_CHECK_status != yajl_gen_status_ok) { \ + return GEN_AND_CHECK_status; \ + } \ + } while (false) + +static inline yajl_gen_status yajl_gen_cstring(yajl_gen gen, const char *str) { + return yajl_gen_string(gen, (const unsigned char*)str, strlen(str)); +} + +static yajl_gen_status append_children_array_json(yajl_gen gen, const JSC::ProfileNode *node); +static yajl_gen_status append_node_json(yajl_gen gen, const JSC::ProfileNode *node); + +static yajl_gen_status append_root_json(yajl_gen gen, const JSC::Profile *profile) { + GEN_AND_CHECK(yajl_gen_map_open(gen)); + GEN_AND_CHECK(yajl_gen_cstring(gen, "rootNodes")); + GEN_AND_CHECK(append_children_array_json(gen, profile->head())); + GEN_AND_CHECK(yajl_gen_map_close(gen)); + + return yajl_gen_status_ok; +} + +static yajl_gen_status append_children_array_json(yajl_gen gen, const JSC::ProfileNode *node) { + GEN_AND_CHECK(yajl_gen_array_open(gen)); + for (RefPtr child : node->children()) { + GEN_AND_CHECK(append_node_json(gen, child.get())); + } + GEN_AND_CHECK(yajl_gen_array_close(gen)); + + return yajl_gen_status_ok; +} + +static yajl_gen_status append_node_json(yajl_gen gen, const JSC::ProfileNode *node) { + GEN_AND_CHECK(yajl_gen_map_open(gen)); + GEN_AND_CHECK(yajl_gen_cstring(gen, "id")); + GEN_AND_CHECK(yajl_gen_integer(gen, node->id())); + + if (!node->functionName().isEmpty()) { + GEN_AND_CHECK(yajl_gen_cstring(gen, "functionName")); + GEN_AND_CHECK(yajl_gen_cstring(gen, node->functionName().utf8().data())); + } + + if (!node->url().isEmpty()) { + GEN_AND_CHECK(yajl_gen_cstring(gen, "url")); + GEN_AND_CHECK(yajl_gen_cstring(gen, node->url().utf8().data())); + GEN_AND_CHECK(yajl_gen_cstring(gen, "lineNumber")); + GEN_AND_CHECK(yajl_gen_integer(gen, node->lineNumber())); + GEN_AND_CHECK(yajl_gen_cstring(gen, "columnNumber")); + GEN_AND_CHECK(yajl_gen_integer(gen, node->columnNumber())); + } + + GEN_AND_CHECK(yajl_gen_cstring(gen, "calls")); + GEN_AND_CHECK(yajl_gen_array_open(gen)); + for (const JSC::ProfileNode::Call &call : node->calls()) { + GEN_AND_CHECK(yajl_gen_map_open(gen)); + GEN_AND_CHECK(yajl_gen_cstring(gen, "startTime")); + GEN_AND_CHECK(yajl_gen_double(gen, call.startTime())); + GEN_AND_CHECK(yajl_gen_cstring(gen, "totalTime")); + GEN_AND_CHECK(yajl_gen_double(gen, call.totalTime())); + GEN_AND_CHECK(yajl_gen_map_close(gen)); + } + GEN_AND_CHECK(yajl_gen_array_close(gen)); + + if (!node->children().isEmpty()) { + GEN_AND_CHECK(yajl_gen_cstring(gen, "children")); + GEN_AND_CHECK(append_children_array_json(gen, node)); + } + + GEN_AND_CHECK(yajl_gen_map_close(gen)); + + return yajl_gen_status_ok; +} + +static char *render_error_code(yajl_gen_status status) { + char err[1024]; + snprintf(err, sizeof(err), "{\"error\": %d}", (int)status); + return strdup(err); +} + +static char *convert_to_json(const JSC::Profile *profile) { + yajl_gen_status status; + yajl_gen gen = yajl_gen_alloc(NULL); + + status = append_root_json(gen, profile); + if (status != yajl_gen_status_ok) { + yajl_gen_free(gen); + return render_error_code(status); + } + + const unsigned char *buf; + size_t buf_size; + status = yajl_gen_get_buf(gen, &buf, &buf_size); + if (status != yajl_gen_status_ok) { + yajl_gen_free(gen); + return render_error_code(status); + } + + char *json_copy = strdup((const char*)buf); + yajl_gen_free(gen); + return json_copy; +} + +static char *JSEndProfilingAndRender(JSContextRef ctx, JSStringRef title) +{ + JSC::ExecState *exec = toJS(ctx); + JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler(); + RefPtr rawProfile = profiler->stopProfiling(exec, title->string()); + return convert_to_json(rawProfile.get()); +} + +JSValueRef nativeProfilerStart( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception) { + if (argumentCount < 1) { + // Could raise an exception here. + return JSValueMakeUndefined(ctx); + } + + JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL); + JSStartProfiling(ctx, title); + JSStringRelease(title); + return JSValueMakeUndefined(ctx); +} + +JSValueRef nativeProfilerEnd( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception) { + if (argumentCount < 1) { + // Could raise an exception here. + return JSValueMakeUndefined(ctx); + } + + JSStringRef title = JSValueToStringCopy(ctx, arguments[0], NULL); + char *rendered = JSEndProfilingAndRender(ctx, title); + JSStringRelease(title); + JSStringRef profile = JSStringCreateWithUTF8CString(rendered); + free(rendered); + return JSValueMakeString(ctx, profile); +} diff --git a/JSCLegacyProfiler/Makefile b/JSCLegacyProfiler/Makefile new file mode 100644 index 000000000..b825f7764 --- /dev/null +++ b/JSCLegacyProfiler/Makefile @@ -0,0 +1,108 @@ +HEADER_PATHS := `find ./tmp/JavaScriptCore -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"` +CERT ?= "iPhone Developer" + +ios8: prepare build generate + +prepare: clean create download + +build: x86_64 arm64 armv7 + +generate: lipo codesign + +clean: + @rm -rf tmp/ /tmp/RCTJSCProfiler + +lipo: + lipo -create -output /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib ./tmp/RCTJSCProfiler_x86_64 ./tmp/RCTJSCProfiler_arm64 ./tmp/RCTJSCProfiler_armv7 + +codesign: + codesign -f -s ${CERT} /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib + +create: + mkdir -p ./tmp /tmp/RCTJSCProfiler/ ./tmp/CoreFoundation ./tmp/Foundation + for file in ./tmp/CoreFoundation/CFUserNotification.h ./tmp/CoreFoundation/CFXMLNode.h ./tmp/CoreFoundation/CFXMLParser.h ./tmp/Foundation/Foundation.h; do echo '' > "$$file"; done + +download: wtf jsc webcore yajl + +wtf: + curl -o tmp/WTF.tar.gz http://www.opensource.apple.com/tarballs/WTF/WTF-7600.1.24.tar.gz + tar -zxvf tmp/WTF.tar.gz -C tmp + +jsc: + curl -o tmp/JSC.tar.gz http://www.opensource.apple.com/tarballs/JavaScriptCore/JavaScriptCore-7600.1.17.tar.gz + tar -zxvf tmp/JSC.tar.gz -C tmp + mv ./tmp/JavaScriptCore-7600.1.17 ./tmp/JavaScriptCore + python ./tmp/JavaScriptCore/generate-bytecode-files --bytecodes_h ./tmp/JavaScriptCore/Bytecodes.h ./tmp/JavaScriptCore/bytecode/BytecodeList.json + +webcore: + curl -o tmp/WebCore.tar.gz http://www.opensource.apple.com/tarballs/WebCore/WebCore-7600.1.25.tar.gz + tar -zxvf tmp/WebCore.tar.gz -C tmp + +yajl: + curl -o tmp/yajl.tar.gz https://codeload.github.com/lloyd/yajl/tar.gz/2.1.0 + tar -zxvf tmp/yajl.tar.gz -C tmp + mkdir -p ./tmp/yajl-2.1.0/build && cd ./tmp/yajl-2.1.0/build && cmake .. && make + echo `find . -name '*.c'` + cd ./tmp/yajl-2.1.0/src && \ + clang -arch arm64 -arch armv7 -std=c99 \ + -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/ \ + -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/machine \ + -I ../build/yajl-2.1.0/include \ + -c `find . -name '*.c'` + libtool -static -o ./tmp/yajl.a `find ./tmp/yajl-2.1.0/src/ -name '*.o'` + +x86_64: + clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_x86_64 -std=c++11 \ + -install_name RCTJSCProfiler.ios8.dylib \ + -include ./tmp/JavaScriptCore/config.h \ + -I ./tmp \ + -I ./tmp/WebCore-7600.1.25/icu \ + -I ./tmp/WTF-7600.1.24 \ + -I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \ + -DNDEBUG=1\ + -miphoneos-version-min=8.0 \ + -L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib \ + -L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/system \ + ${HEADER_PATHS} \ + -undefined dynamic_lookup \ + ./JSCLegacyProfiler.mm ./tmp/yajl-2.1.0/build/yajl-2.1.0/lib/libyajl_s.a + +arm64: + echo $(HEADER_PATHS) + clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_arm64 -std=c++11 \ + -install_name RCTJSCProfiler.ios8.dylib \ + -arch arm64 \ + -include ./tmp/JavaScriptCore/config.h \ + -I ./tmp \ + -I ./tmp/WebCore-7600.1.25/icu \ + -I ./tmp/WTF-7600.1.24 \ + -I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \ + -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include \ + -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include/machine \ + -DNDEBUG=1\ + -miphoneos-version-min=8.0 \ + -L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib \ + -L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/system \ + ${HEADER_PATHS} \ + -undefined dynamic_lookup \ + ./JSCLegacyProfiler.mm ./tmp/yajl.a + +armv7: + clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_armv7 -std=c++11 \ + -install_name RCTJSCProfiler.ios8.dylib \ + -arch armv7 \ + -include ./tmp/JavaScriptCore/config.h \ + -I ./tmp \ + -I ./tmp/WebCore-7600.1.25/icu \ + -I ./tmp/WTF-7600.1.24 \ + -I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \ + -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/include \ + -DNDEBUG=1\ + -miphoneos-version-min=8.0 \ + -L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib \ + -L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS8.3.sdk/usr/lib/system \ + ${HEADER_PATHS} \ + -undefined dynamic_lookup \ + ./JSCLegacyProfiler.mm ./tmp/yajl.a + +.PHONY: ios8 diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 173179c5a..67232489c 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -20,6 +20,22 @@ #import "RCTPerformanceLogger.h" #import "RCTUtils.h" +#ifndef RCT_JSC_PROFILER +#if RCT_DEV && DEBUG +#define RCT_JSC_PROFILER 1 +#else +#define RCT_JSC_PROFILER 0 +#endif +#endif + +#if RCT_JSC_PROFILER +#include + +#ifndef RCT_JSC_PROFILER_DYLIB +#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.ios%zd", [[NSProcessInfo processInfo] operatingSystemVersion].majorVersion] ofType:@"dylib" inDirectory:@"Frameworks"] UTF8String] +#endif +#endif + @interface RCTJavaScriptContext : NSObject @property (nonatomic, assign, readonly) JSGlobalContextRef ctx; @@ -269,6 +285,18 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) [strongSelf _addNativeHook:RCTConsoleProfile withName:"consoleProfile"]; [strongSelf _addNativeHook:RCTConsoleProfileEnd withName:"consoleProfileEnd"]; +#if RCT_JSC_PROFILER + void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW); + if (JSCProfiler != NULL) { + JSObjectCallAsFunctionCallback nativeProfilerStart = dlsym(JSCProfiler, "nativeProfilerStart"); + JSObjectCallAsFunctionCallback nativeProfilerEnd = dlsym(JSCProfiler, "nativeProfilerEnd"); + if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) { + [strongSelf _addNativeHook:nativeProfilerStart withName:"nativeProfilerStart"]; + [strongSelf _addNativeHook:nativeProfilerEnd withName:"nativeProfilerStop"]; + } + } +#endif + for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { [[NSNotificationCenter defaultCenter] addObserver:strongSelf selector:@selector(toggleProfilingFlag:) diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 1e193d9b2..2c551600b 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -472,6 +472,7 @@ 83CBBA2A1A601D0E00E9B192 /* Sources */, 83CBBA2B1A601D0E00E9B192 /* Frameworks */, 83CBBA2C1A601D0E00E9B192 /* Copy Files */, + 142C4F7F1B582EA6001F0B58 /* ShellScript */, ); buildRules = ( ); @@ -528,6 +529,20 @@ shellPath = /bin/sh; shellScript = "if nc -w 5 -z localhost 8081 ; then\n if ! curl -s \"http://localhost:8081/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port 8081 already in use, packager is either not running or not running correctly\"\n exit 2\n fi\nelse\n open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"\nfi"; }; + 142C4F7F1B582EA6001F0B58 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ \"$CONFIGURATION\" == \"Debug\" ]] && [[ -d \"/tmp/RCTJSCProfiler\" ]]; then\n find \"${CONFIGURATION_BUILD_DIR}\" -name '*.app' | xargs -I{} sh -c 'mkdir -p \"$1/Frameworks\" && cp -r /tmp/RCTJSCProfiler/* \"$1/Frameworks\"' -- {}\nfi"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */