Update json generation code and save to file

Summary: @​public

* Change the JSON generation and remove the dependency on YAJL since it had a
128 depth limit
* Enable the profiler bytecode generation to fix missing frames
* Save the output to a file on the tmp dir instead of outputting it to the console

Reviewed By: @jspahrsummers

Differential Revision: D2420754
This commit is contained in:
Tadeu Zagallo 2015-09-10 09:03:03 -07:00 committed by facebook-github-bot-9
parent 2a34239c17
commit af05af7125
4 changed files with 284 additions and 119 deletions

View File

@ -6,7 +6,8 @@
extern "C" {
void nativeProfilerEnableBytecode(void);
void nativeProfilerStart(JSContextRef ctx, const char *title);
const char *nativeProfilerEnd(JSContextRef ctx, const char *title);
void nativeProfilerEnd(JSContextRef ctx, const char *title, const char *filename);
}

View File

@ -6,125 +6,298 @@
#include "JSProfilerPrivate.h"
#include "JSStringRef.h"
#include "String.h"
#include "Options.h"
#include <YAJL/yajl_gen.h>
enum json_gen_status {
json_gen_status_ok = 0,
json_gen_status_error = 1,
};
enum json_entry {
json_entry_key,
json_entry_value,
};
namespace {
struct json_state {
FILE *fileOut;
bool hasFirst;
};
}
typedef json_state *json_gen;
static void json_escaped_cstring_printf(json_gen gen, const char *str) {
const char *cursor = str;
fputc('"', gen->fileOut);
while (*cursor) {
const char *escape = nullptr;
switch (*cursor) {
case '"':
escape = "\\\"";
break;
case '\b':
escape = "\\b";
break;
case '\f':
escape = "\\f";
break;
case '\n':
escape = "\\n";
break;
case '\r':
escape = "\\r";
break;
case '\t':
escape = "\\t";
break;
case '\\':
escape = "\\\\";
break;
default:
break;
}
if (escape != nullptr) {
fwrite(escape, 1, strlen(escape), gen->fileOut);
} else {
fputc(*cursor, gen->fileOut);
}
cursor++;
}
fputc('"', gen->fileOut);
}
static json_gen_status json_gen_key_cstring(json_gen gen, const char *buffer) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
if (gen->hasFirst) {
fprintf(gen->fileOut, ",");
}
gen->hasFirst = true;
json_escaped_cstring_printf(gen, buffer);
return json_gen_status_ok;
}
static json_gen_status json_gen_map_open(json_gen gen, json_entry entryType) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
if (entryType == json_entry_value) {
fprintf(gen->fileOut, ":");
} else if (entryType == json_entry_key) {
if (gen->hasFirst) {
fprintf(gen->fileOut, ",");
}
}
fprintf(gen->fileOut, "{");
gen->hasFirst = false;
return json_gen_status_ok;
}
static json_gen_status json_gen_map_close(json_gen gen) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
fprintf(gen->fileOut, "}");
gen->hasFirst = true;
return json_gen_status_ok;
}
static json_gen_status json_gen_array_open(json_gen gen, json_entry entryType) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
if (entryType == json_entry_value) {
fprintf(gen->fileOut, ":");
} else if (entryType == json_entry_key) {
if (gen->hasFirst) {
fprintf(gen->fileOut, ",");
}
}
fprintf(gen->fileOut, "[");
gen->hasFirst = false;
return json_gen_status_ok;
}
static json_gen_status json_gen_array_close(json_gen gen) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
fprintf(gen->fileOut, "]");
gen->hasFirst = true;
return json_gen_status_ok;
}
static json_gen_status json_gen_keyvalue_cstring(json_gen gen, const char *key, const char *value) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
if (gen->hasFirst) {
fprintf(gen->fileOut, ",");
}
gen->hasFirst = true;
fprintf(gen->fileOut, "\"%s\" : ", key);
json_escaped_cstring_printf(gen, value);
return json_gen_status_ok;
}
static json_gen_status json_gen_keyvalue_integer(json_gen gen, const char *key, int value) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
if (gen->hasFirst) {
fprintf(gen->fileOut, ",");
}
gen->hasFirst = true;
fprintf(gen->fileOut, "\"%s\": %d", key, value);
return json_gen_status_ok;
}
static json_gen_status json_gen_keyvalue_double(json_gen gen, const char *key, double value) {
if (gen->fileOut == nullptr) {
return json_gen_status_error;
}
if (gen->hasFirst) {
fprintf(gen->fileOut, ",");
}
gen->hasFirst = true;
fprintf(gen->fileOut, "\"%s\": %.20g", key, value);
return json_gen_status_ok;
}
static json_gen json_gen_alloc(const char *fileName) {
json_gen gen = (json_gen)malloc(sizeof(json_state));
memset(gen, 0, sizeof(json_state));
gen->fileOut = fopen(fileName, "wb");
return gen;
}
static void json_gen_free(json_gen gen) {
if (gen->fileOut) {
fclose(gen->fileOut);
}
free(gen);
}
#define GEN_AND_CHECK(expr) \
do { \
yajl_gen_status GEN_AND_CHECK_status = (expr); \
if (GEN_AND_CHECK_status != yajl_gen_status_ok) { \
json_gen_status GEN_AND_CHECK_status = (expr); \
if (GEN_AND_CHECK_status != json_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 json_gen_status append_children_array_json(json_gen gen, const JSC::ProfileNode *node);
static json_gen_status append_node_json(json_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"));
static json_gen_status append_root_json(json_gen gen, const JSC::Profile *profile) {
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
GEN_AND_CHECK(json_gen_key_cstring(gen, "rootNodes"));
GEN_AND_CHECK(append_children_array_json(gen, profile->head()));
GEN_AND_CHECK(yajl_gen_map_close(gen));
GEN_AND_CHECK(json_gen_map_close(gen));
return yajl_gen_status_ok;
return json_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));
static json_gen_status append_children_array_json(json_gen gen, const JSC::ProfileNode *node) {
GEN_AND_CHECK(json_gen_array_open(gen, json_entry_value));
for (RefPtr<JSC::ProfileNode> child : node->children()) {
GEN_AND_CHECK(append_node_json(gen, child.get()));
}
GEN_AND_CHECK(yajl_gen_array_close(gen));
GEN_AND_CHECK(json_gen_array_close(gen));
return yajl_gen_status_ok;
return json_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()));
static json_gen_status append_node_json(json_gen gen, const JSC::ProfileNode *node) {
GEN_AND_CHECK(json_gen_map_open(gen, json_entry_key));
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "id", 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()));
GEN_AND_CHECK(json_gen_keyvalue_cstring(gen, "functionName", 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(json_gen_keyvalue_cstring(gen, "url", node->url().utf8().data()));
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "lineNumber", node->lineNumber()));
GEN_AND_CHECK(json_gen_keyvalue_integer(gen, "columnNumber", node->columnNumber()));
}
GEN_AND_CHECK(yajl_gen_cstring(gen, "calls"));
GEN_AND_CHECK(yajl_gen_array_open(gen));
GEN_AND_CHECK(json_gen_key_cstring(gen, "calls"));
GEN_AND_CHECK(json_gen_array_open(gen, json_entry_value));
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(json_gen_map_open(gen, json_entry_key));
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "startTime", call.startTime()));
GEN_AND_CHECK(json_gen_keyvalue_double(gen, "totalTime", call.totalTime()));
GEN_AND_CHECK(json_gen_map_close(gen));
}
GEN_AND_CHECK(yajl_gen_array_close(gen));
GEN_AND_CHECK(json_gen_array_close(gen));
if (!node->children().isEmpty()) {
GEN_AND_CHECK(yajl_gen_cstring(gen, "children"));
GEN_AND_CHECK(json_gen_key_cstring(gen, "children"));
GEN_AND_CHECK(append_children_array_json(gen, node));
}
GEN_AND_CHECK(yajl_gen_map_close(gen));
GEN_AND_CHECK(json_gen_map_close(gen));
return yajl_gen_status_ok;
return json_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);
static void convert_to_json(const JSC::Profile *profile, const char *filename) {
json_gen_status status;
json_gen gen = json_gen_alloc(filename);
status = append_root_json(gen, profile);
if (status != yajl_gen_status_ok) {
yajl_gen_free(gen);
return render_error_code(status);
if (status != json_gen_status_ok) {
FILE *fileOut = fopen(filename, "wb");
if (fileOut != nullptr) {
fprintf(fileOut, "{\"error\": %d}", (int)status);
fclose(fileOut);
}
}
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;
json_gen_free(gen);
}
static const char *JSEndProfilingAndRender(JSContextRef ctx, const char *title)
// Based on JSEndProfiling, with a little extra code to return the profile as JSON.
static void JSEndProfilingAndRender(JSContextRef ctx, const char *title, const char *filename)
{
JSC::ExecState *exec = toJS(ctx);
JSC::LegacyProfiler *profiler = JSC::LegacyProfiler::profiler();
RefPtr<JSC::Profile> rawProfile = profiler->stopProfiling(exec, WTF::String(title));
return convert_to_json(rawProfile.get());
convert_to_json(rawProfile.get(), filename);
}
extern "C" {
void nativeProfilerEnableBytecode(void)
{
JSC::Options::setOption("forceProfilerBytecodeGeneration=true");
}
void nativeProfilerStart(JSContextRef ctx, const char *title) {
JSStartProfiling(ctx, JSStringCreateWithUTF8CString(title));
}
const char *nativeProfilerEnd( JSContextRef ctx, const char *title) {
return JSEndProfilingAndRender(ctx, title);
void nativeProfilerEnd(JSContextRef ctx, const char *title, const char *filename) {
JSEndProfilingAndRender(ctx, title, filename);
}
}

View File

@ -17,7 +17,7 @@ PLATFORM = \
SYSROOT = -isysroot $(call SDK_PATH,$${PLATFORM})
IOS8_LIBS = download/WebCore/WebCore-7600.1.25 download/WTF/WTF-7600.1.24 download/JavaScriptCore/JavaScriptCore-7600.1.17 download/JavaScriptCore/JavaScriptCore-7600.1.17/Bytecodes.h libyajl.a
IOS8_LIBS = download/WebCore/WebCore-7600.1.25 download/WTF/WTF-7600.1.24 download/JavaScriptCore/JavaScriptCore-7600.1.17 download/JavaScriptCore/JavaScriptCore-7600.1.17/Bytecodes.h
ios8: RCTJSCProfiler.ios8.dylib /tmp/RCTJSCProfiler
ifneq ($(SDK_VERSION), 8)
@ -47,43 +47,17 @@ RCTJSCProfiler_%.ios8.dylib: $(IOS8_LIBS)
-I download \
-I download/WebCore/WebCore-7600.1.25/icu \
-I download/WTF/WTF-7600.1.24 \
-I download/yajl-2.1.0/build/yajl-2.1.0/include \
-DNDEBUG=1\
-miphoneos-version-min=8.0 \
$(SYSROOT) \
$(HEADER_PATHS) \
-undefined dynamic_lookup \
JSCLegacyProfiler.mm libyajl.a
JSCLegacyProfiler.mm
.PRECIOUS: %/Bytecodes.h
%/Bytecodes.h:
python $*/generate-bytecode-files --bytecodes_h $@ $*/bytecode/BytecodeList.json
.PRECIOUS: libyajl.a
libyajl.a: $(patsubst %,libyajl_%.a,$(ARCHS))
lipo -create $^ -output $@
.PRECIOUS: libyajl_%.a
libyajl_%.a: download/yajl-2.1.0
$(PLATFORM) \
cd download/yajl-2.1.0/src; \
clang -arch $(*F) -std=c99 \
-miphoneos-version-min=8.0 \
$(SYSROOT) \
-I ../build/yajl-2.1.0/include \
-c `find . -name '*.c'`
find download/yajl-2.1.0/src/ -name '*.o' -exec libtool -static -o $@ {} +
.PRECIOUS: download/yajl-2.1.0
download/yajl-2.1.0: download/yajl-2.1.0.tar.gz
tar -zxvf $< -C download > /dev/null
mkdir -p download/yajl-2.1.0/build && cd download/yajl-2.1.0/build && cmake ..
.PRECIOUS: download/yajl-2.1.0.tar.gz
download/yajl-2.1.0.tar.gz:
mkdir -p `dirname $@`
curl -o $@ https://codeload.github.com/lloyd/yajl/tar.gz/2.1.0
.PRECIOUS: download/%
download/%: download/%.tar.gz
tar -zxvf $< -C `dirname $@` > /dev/null
@ -95,6 +69,6 @@ download/%: download/%.tar.gz
.PHONY: clean
clean:
@rm -rf $(wildcard *.dylib)
@rm -rf $(wildcard *.a)
@rm -rf download
-rm -rf $(wildcard *.dylib)
-rm -rf $(wildcard *.a)
-rm -rf download

View File

@ -206,6 +206,40 @@ static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObje
return JSValueMakeUndefined(context);
}
static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
{
#if RCT_JSC_PROFILER
void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
if (JSCProfiler != NULL) {
void (*nativeProfilerStart)(JSContextRef, const char *) =
(__typeof__(nativeProfilerStart))dlsym(JSCProfiler, "nativeProfilerStart");
void (*nativeProfilerEnd)(JSContextRef, const char *, const char *) =
(__typeof__(nativeProfilerEnd))dlsym(JSCProfiler, "nativeProfilerEnd");
if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) {
void (*nativeProfilerEnableByteCode)(void) =
(__typeof__(nativeProfilerEnableByteCode))dlsym(JSCProfiler, "nativeProfilerEnableByteCode");
if (nativeProfilerEnableByteCode != NULL) {
nativeProfilerEnableByteCode();
}
__block BOOL isProfiling = NO;
[bridge.devMenu addItem:@"Profile" handler:^{
if (isProfiling) {
NSString *outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cpu_profile.json"];
nativeProfilerEnd(context, "profile", outputFile.UTF8String);
RCTLogInfo(@"CPU profile outputed to '%@'", outputFile);
} else {
nativeProfilerStart(context, "profile");
}
isProfiling = !isProfiling;
}];
}
}
#endif
}
#endif
+ (void)runRunLoopThread
@ -284,24 +318,7 @@ static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObje
[strongSelf _addNativeHook:RCTNativeTraceBeginSection withName:"nativeTraceBeginSection"];
[strongSelf _addNativeHook:RCTNativeTraceEndSection withName:"nativeTraceEndSection"];
#if RCT_JSC_PROFILER
void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
if (JSCProfiler != NULL) {
void (*nativeProfilerStart)(JSContextRef, const char *) = (void (*)(JSContextRef, const char *))dlsym(JSCProfiler, "nativeProfilerStart");
const char *(*nativeProfilerEnd)(JSContextRef, const char *) = (const char *(*)(JSContextRef, const char *))dlsym(JSCProfiler, "nativeProfilerEnd");
if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) {
__block BOOL isProfiling = NO;
[_bridge.devMenu addItem:@"Profile" handler:^{
if (isProfiling) {
RCTLogInfo(@"%s", nativeProfilerEnd(strongSelf->_context.ctx, "profile"));
} else {
nativeProfilerStart(strongSelf->_context.ctx, "profile");
}
isProfiling = !isProfiling;
}];
}
}
#endif
RCTInstallJSCProfiler(_bridge, strongSelf->_context.ctx);
for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
[[NSNotificationCenter defaultCenter] addObserver:strongSelf