483 lines
14 KiB
C++
483 lines
14 KiB
C++
// Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
#ifdef WITH_JSC_EXTRA_TRACING
|
|
|
|
#include <algorithm>
|
|
#include <JavaScriptCore/JavaScript.h>
|
|
#include <JavaScriptCore/API/JSProfilerPrivate.h>
|
|
#include <fbsystrace.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include "JSCHelpers.h"
|
|
|
|
using std::min;
|
|
|
|
static const char *ENABLED_FBSYSTRACE_PROFILE_NAME = "__fbsystrace__";
|
|
|
|
static uint64_t tagFromJSValue(
|
|
JSContextRef ctx,
|
|
JSValueRef value,
|
|
JSValueRef* exception) {
|
|
// XXX validate that this is a lossless conversion.
|
|
// XXX should we just have separate functions for bridge, infra, and apps,
|
|
// then drop this argument to save time?
|
|
(void)exception;
|
|
uint64_t tag = (uint64_t) JSValueToNumber(ctx, value, NULL);
|
|
return tag;
|
|
}
|
|
|
|
static int64_t int64FromJSValue(
|
|
JSContextRef ctx,
|
|
JSValueRef value,
|
|
JSValueRef* exception) {
|
|
(void)exception;
|
|
int64_t num = (int64_t) JSValueToNumber(ctx, value, NULL);
|
|
return num;
|
|
}
|
|
|
|
static size_t copyTruncatedAsciiChars(
|
|
char* buf,
|
|
size_t bufLen,
|
|
JSContextRef ctx,
|
|
JSValueRef value,
|
|
size_t maxLen) {
|
|
JSStringRef jsString = JSValueToStringCopy(ctx, value, NULL);
|
|
size_t stringLen = JSStringGetLength(jsString);
|
|
// Unlike the Java version, we truncate from the end of the string,
|
|
// rather than the beginning.
|
|
size_t toWrite = min(stringLen, min(bufLen, maxLen));
|
|
|
|
const char *startBuf = buf;
|
|
const JSChar* chars = JSStringGetCharactersPtr(jsString);
|
|
while (toWrite-- > 0) {
|
|
*(buf++) = (char)*(chars++);
|
|
}
|
|
|
|
JSStringRelease(jsString);
|
|
|
|
// Return the number of bytes written
|
|
return buf - startBuf;
|
|
}
|
|
|
|
static size_t copyArgsToBuffer(
|
|
char* buf,
|
|
size_t bufLen,
|
|
size_t pos,
|
|
JSContextRef ctx,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[]) {
|
|
char separator = '|';
|
|
for (
|
|
size_t idx = 0;
|
|
idx + 1 < argumentCount; // Make sure key and value are present.
|
|
idx += 2) {
|
|
JSValueRef key = arguments[idx];
|
|
JSValueRef value = arguments[idx+1];
|
|
|
|
buf[pos++] = separator;
|
|
separator = ';';
|
|
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
|
|
pos += copyTruncatedAsciiChars(
|
|
buf + pos, bufLen - pos, ctx, key, FBSYSTRACE_MAX_MESSAGE_LENGTH);
|
|
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
|
|
buf[pos++] = '=';
|
|
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
|
|
pos += copyTruncatedAsciiChars(
|
|
buf + pos, bufLen - pos, ctx, value, FBSYSTRACE_MAX_MESSAGE_LENGTH);
|
|
if (FBSYSTRACE_UNLIKELY(pos >= bufLen)) { break; }
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
static JSValueRef nativeTraceBeginSection(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_UNLIKELY(argumentCount < 2)) {
|
|
if (exception) {
|
|
*exception = facebook::react::makeJSCException(
|
|
ctx,
|
|
"nativeTraceBeginSection: requires at least 2 arguments");
|
|
}
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
|
|
size_t pos = 0;
|
|
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "B|%d|", getpid());
|
|
// Skip the overflow check here because the int will be small.
|
|
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
|
|
// Skip the overflow check here because the section name will be small-ish.
|
|
|
|
pos = copyArgsToBuffer(buf, sizeof(buf), pos, ctx, argumentCount - 2, arguments + 2);
|
|
if (FBSYSTRACE_UNLIKELY(pos >= sizeof(buf))) {
|
|
goto flush;
|
|
}
|
|
|
|
flush:
|
|
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
static JSValueRef nativeTraceEndSection(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_UNLIKELY(argumentCount < 1)) {
|
|
if (exception) {
|
|
*exception = facebook::react::makeJSCException(
|
|
ctx,
|
|
"nativeTraceEndSection: requires at least 1 argument");
|
|
}
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
if (FBSYSTRACE_LIKELY(argumentCount == 1)) {
|
|
fbsystrace_end_section(tag);
|
|
} else {
|
|
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
|
|
size_t pos = 0;
|
|
|
|
buf[pos++] = 'E';
|
|
buf[pos++] = '|';
|
|
buf[pos++] = '|';
|
|
pos = copyArgsToBuffer(buf, sizeof(buf), pos, ctx, argumentCount - 1, arguments + 1);
|
|
if (FBSYSTRACE_UNLIKELY(pos >= sizeof(buf))) {
|
|
goto flush;
|
|
}
|
|
|
|
flush:
|
|
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
|
|
}
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
static JSValueRef beginOrEndAsync(
|
|
bool isEnd,
|
|
bool isFlow,
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) {
|
|
if (exception) {
|
|
*exception = facebook::react::makeJSCException(
|
|
ctx,
|
|
"beginOrEndAsync: requires at least 3 arguments");
|
|
}
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
|
|
size_t pos = 0;
|
|
|
|
// This uses an if-then-else instruction in ARMv7, which should be cheaper
|
|
// than a full branch.
|
|
buf[pos++] = ((isFlow) ? (isEnd ? 'f' : 's') : (isEnd ? 'F' : 'S'));
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d|", getpid());
|
|
// Skip the overflow check here because the int will be small.
|
|
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
|
|
// Skip the overflow check here because the section name will be small-ish.
|
|
|
|
// I tried some trickery to avoid a branch here, but gcc did not cooperate.
|
|
// We could consider changing the implementation to be lest branchy in the
|
|
// future.
|
|
// This is not required for flow use an or to avoid introducing another branch
|
|
if (!(isEnd | isFlow)) {
|
|
buf[pos++] = '<';
|
|
buf[pos++] = '0';
|
|
buf[pos++] = '>';
|
|
}
|
|
buf[pos++] = '|';
|
|
|
|
// Append the cookie. It should be an integer, but copyTruncatedAsciiChars
|
|
// will automatically convert it to a string. We might be able to get more
|
|
// performance by just getting the number and doing to string
|
|
// conversion ourselves. We truncate to FBSYSTRACE_MAX_SECTION_NAME_LENGTH
|
|
// just to make sure we can avoid the overflow check even if the caller
|
|
// passes in something bad.
|
|
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[2], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
|
|
|
|
pos = copyArgsToBuffer(buf, sizeof(buf), pos, ctx, argumentCount - 3, arguments + 3);
|
|
if (FBSYSTRACE_UNLIKELY(pos >= sizeof(buf))) {
|
|
goto flush;
|
|
}
|
|
|
|
flush:
|
|
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
static JSValueRef stageAsync(
|
|
bool isFlow,
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_UNLIKELY(argumentCount < 4)) {
|
|
if (exception) {
|
|
*exception = facebook::react::makeJSCException(
|
|
ctx,
|
|
"stageAsync: requires at least 4 arguments");
|
|
}
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
|
|
size_t pos = 0;
|
|
|
|
buf[pos++] = (isFlow ? 't' : 'T');
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "|%d", getpid());
|
|
// Skip the overflow check here because the int will be small.
|
|
|
|
// Arguments are section name, cookie, and stage name.
|
|
// All added together, they still cannot cause an overflow.
|
|
for (int i = 1; i < 4; i++) {
|
|
buf[pos++] = '|';
|
|
pos += copyTruncatedAsciiChars(buf + pos, sizeof(buf) - pos, ctx, arguments[i], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
|
|
}
|
|
|
|
fbsystrace_trace_raw(buf, min(pos, sizeof(buf)-1));
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
static JSValueRef nativeTraceBeginAsyncSection(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
return beginOrEndAsync(
|
|
false /* isEnd */,
|
|
false /* isFlow */,
|
|
ctx,
|
|
function,
|
|
thisObject,
|
|
argumentCount,
|
|
arguments,
|
|
exception);
|
|
}
|
|
|
|
static JSValueRef nativeTraceEndAsyncSection(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
return beginOrEndAsync(
|
|
true /* isEnd */,
|
|
false /* isFlow */,
|
|
ctx,
|
|
function,
|
|
thisObject,
|
|
argumentCount,
|
|
arguments,
|
|
exception);
|
|
}
|
|
|
|
static JSValueRef nativeTraceAsyncSectionStage(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
return stageAsync(
|
|
false /* isFlow */,
|
|
ctx,
|
|
function,
|
|
thisObject,
|
|
argumentCount,
|
|
arguments,
|
|
exception);
|
|
}
|
|
|
|
static JSValueRef nativeTraceBeginAsyncFlow(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
return beginOrEndAsync(
|
|
false /* isEnd */,
|
|
true /* isFlow */,
|
|
ctx,
|
|
function,
|
|
thisObject,
|
|
argumentCount,
|
|
arguments,
|
|
exception);
|
|
}
|
|
|
|
static JSValueRef nativeTraceEndAsyncFlow(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
return beginOrEndAsync(
|
|
true /* isEnd */,
|
|
true /* isFlow */,
|
|
ctx,
|
|
function,
|
|
thisObject,
|
|
argumentCount,
|
|
arguments,
|
|
exception);
|
|
}
|
|
|
|
static JSValueRef nativeTraceAsyncFlowStage(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
return stageAsync(
|
|
true /* isFlow */,
|
|
ctx,
|
|
function,
|
|
thisObject,
|
|
argumentCount,
|
|
arguments,
|
|
exception);
|
|
}
|
|
|
|
static JSValueRef nativeTraceCounter(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_UNLIKELY(argumentCount < 3)) {
|
|
if (exception) {
|
|
*exception = facebook::react::makeJSCException(
|
|
ctx,
|
|
"nativeTraceCounter: requires at least 3 arguments");
|
|
}
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
char buf[FBSYSTRACE_MAX_MESSAGE_LENGTH];
|
|
size_t len = copyTruncatedAsciiChars(buf, sizeof(buf), ctx,
|
|
arguments[1], FBSYSTRACE_MAX_SECTION_NAME_LENGTH);
|
|
buf[min(len,(FBSYSTRACE_MAX_MESSAGE_LENGTH-1))] = 0;
|
|
int64_t value = int64FromJSValue(ctx, arguments[2], exception);
|
|
|
|
fbsystrace_counter(tag, buf, value);
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
static JSValueRef nativeTraceBeginLegacy(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_LIKELY(argumentCount >= 1)) {
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
}
|
|
|
|
JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME);
|
|
#if WITH_REACT_INTERNAL_SETTINGS
|
|
JSStartProfiling(ctx, title, true);
|
|
#else
|
|
JSStartProfiling(ctx, title);
|
|
#endif
|
|
JSStringRelease(title);
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
static JSValueRef nativeTraceEndLegacy(
|
|
JSContextRef ctx,
|
|
JSObjectRef function,
|
|
JSObjectRef thisObject,
|
|
size_t argumentCount,
|
|
const JSValueRef arguments[],
|
|
JSValueRef* exception) {
|
|
if (FBSYSTRACE_LIKELY(argumentCount >= 1)) {
|
|
uint64_t tag = tagFromJSValue(ctx, arguments[0], exception);
|
|
if (!fbsystrace_is_tracing(tag)) {
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
}
|
|
|
|
JSStringRef title = JSStringCreateWithUTF8CString(ENABLED_FBSYSTRACE_PROFILE_NAME);
|
|
JSEndProfiling(ctx, title);
|
|
JSStringRelease(title);
|
|
|
|
return JSValueMakeUndefined(ctx);
|
|
}
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
void addNativeTracingHooks(JSGlobalContextRef ctx) {
|
|
installGlobalFunction(ctx, "nativeTraceBeginSection", nativeTraceBeginSection);
|
|
installGlobalFunction(ctx, "nativeTraceEndSection", nativeTraceEndSection);
|
|
installGlobalFunction(ctx, "nativeTraceBeginLegacy", nativeTraceBeginLegacy);
|
|
installGlobalFunction(ctx, "nativeTraceEndLegacy", nativeTraceEndLegacy);
|
|
installGlobalFunction(ctx, "nativeTraceBeginAsyncSection", nativeTraceBeginAsyncSection);
|
|
installGlobalFunction(ctx, "nativeTraceEndAsyncSection", nativeTraceEndAsyncSection);
|
|
installGlobalFunction(ctx, "nativeTraceAsyncSectionStage", nativeTraceAsyncSectionStage);
|
|
installGlobalFunction(ctx, "nativeTraceBeginAsyncFlow", nativeTraceBeginAsyncFlow);
|
|
installGlobalFunction(ctx, "nativeTraceEndAsyncFlow", nativeTraceEndAsyncFlow);
|
|
installGlobalFunction(ctx, "nativeTraceAsyncFlowStage", nativeTraceAsyncFlowStage);
|
|
installGlobalFunction(ctx, "nativeTraceCounter", nativeTraceCounter);
|
|
}
|
|
|
|
} }
|
|
|
|
#endif
|