Bug 1072910 - TraceLogger: Create hooks for the debugger, r=bbouvier

This commit is contained in:
Hannes Verschore 2014-11-20 17:44:02 +01:00
Родитель 0b6b4a1006
Коммит f60f8b9d68
4 изменённых файлов: 301 добавлений и 3 удалений

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

@ -45,7 +45,6 @@ using js::frontend::IsIdentifier;
using mozilla::ArrayLength; using mozilla::ArrayLength;
using mozilla::Maybe; using mozilla::Maybe;
/*** Forward declarations ************************************************************************/ /*** Forward declarations ************************************************************************/
extern const Class DebuggerFrame_class; extern const Class DebuggerFrame_class;
@ -360,7 +359,13 @@ Debugger::Debugger(JSContext *cx, NativeObject *dbg)
scripts(cx), scripts(cx),
sources(cx), sources(cx),
objects(cx), objects(cx),
environments(cx) environments(cx),
#ifdef NIGHTLY_BUILD
traceLoggerLastDrainedId(0),
traceLoggerLastDrainedIteration(0),
#endif
traceLoggerScriptedCallsLastDrainedId(0),
traceLoggerScriptedCallsLastDrainedIteration(0)
{ {
assertSameCompartment(cx, dbg); assertSameCompartment(cx, dbg);
@ -3771,6 +3776,18 @@ Debugger::makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp)
return dbg->wrapDebuggeeValue(cx, args.rval()); return dbg->wrapDebuggeeValue(cx, args.rval());
} }
static bool
DefineProperty(JSContext *cx, HandleObject obj, HandleId id, const char *value, size_t n)
{
JSString *text = JS_NewStringCopyN(cx, value, n);
if (!text)
return false;
RootedValue str(cx, StringValue(text));
return JS_DefinePropertyById(cx, obj, id, str, JSPROP_ENUMERATE);
}
#ifdef NIGHTLY_BUILD
bool bool
Debugger::setupTraceLogger(JSContext *cx, unsigned argc, Value *vp) Debugger::setupTraceLogger(JSContext *cx, unsigned argc, Value *vp)
{ {
@ -3839,6 +3856,169 @@ Debugger::setupTraceLogger(JSContext *cx, unsigned argc, Value *vp)
return true; return true;
} }
bool
Debugger::drainTraceLogger(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "drainTraceLogger", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.drainTraceLogger", 0))
return false;
size_t num;
TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime());
bool lostEvents = logger->lostEvents(dbg->traceLoggerLastDrainedIteration,
dbg->traceLoggerLastDrainedId);
EventEntry *events = logger->getEventsStartingAt(&dbg->traceLoggerLastDrainedIteration,
&dbg->traceLoggerLastDrainedId,
&num);
RootedObject array(cx, NewDenseEmptyArray(cx));
JSAtom *dataAtom = Atomize(cx, "data", strlen("data"));
if (!dataAtom)
return false;
RootedId dataId(cx, AtomToId(dataAtom));
/* Add all events to the array. */
uint32_t index = 0;
for (EventEntry *eventItem = events; eventItem < events + num; eventItem++, index++) {
RootedObject item(cx, NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, cx->global()));
if (!item)
return false;
const char *eventText = logger->eventText(eventItem->textId);
if (!DefineProperty(cx, item, dataId, eventText, strlen(eventText)))
return false;
RootedValue obj(cx, ObjectValue(*item));
if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE))
return false;
}
/* Add "lostEvents" indicating if there are events that were lost. */
RootedValue lost(cx, BooleanValue(lostEvents));
if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE))
return false;
args.rval().setObject(*array);
return true;
}
#endif
bool
Debugger::setupTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "setupTraceLoggerScriptCalls", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.setupTraceLoggerScriptCalls", 0))
return false;
TraceLogEnableTextId(cx, TraceLogger_Scripts);
args.rval().setBoolean(true);
return true;
}
bool
Debugger::startTraceLogger(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "startTraceLogger", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.startTraceLogger", 0))
return false;
TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime());
TraceLoggerEnable(logger, cx);
args.rval().setUndefined();
return true;
}
bool
Debugger::endTraceLogger(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "endTraceLogger", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.endTraceLogger", 0))
return false;
TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime());
TraceLoggerDisable(logger);
args.rval().setUndefined();
return true;
}
bool
Debugger::drainTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp)
{
THIS_DEBUGGER(cx, argc, vp, "drainTraceLoggerScriptCalls", args, dbg);
if (!args.requireAtLeast(cx, "Debugger.drainTraceLoggerScriptCalls", 0))
return false;
size_t num;
TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime());
bool lostEvents = logger->lostEvents(dbg->traceLoggerScriptedCallsLastDrainedIteration,
dbg->traceLoggerScriptedCallsLastDrainedId);
EventEntry *events = logger->getEventsStartingAt(
&dbg->traceLoggerScriptedCallsLastDrainedIteration,
&dbg->traceLoggerScriptedCallsLastDrainedId,
&num);
RootedObject array(cx, NewDenseEmptyArray(cx));
RootedId fileNameId(cx, AtomToId(cx->names().fileName));
RootedId lineNumberId(cx, AtomToId(cx->names().lineNumber));
RootedId columnNumberId(cx, AtomToId(cx->names().columnNumber));
JSAtom *logTypeAtom = Atomize(cx, "logType", strlen("logType"));
if (!logTypeAtom)
return false;
RootedId logTypeId(cx, AtomToId(logTypeAtom));
/* Add all events to the array. */
uint32_t index = 0;
for (EventEntry *eventItem = events; eventItem < events + num; eventItem++) {
RootedObject item(cx, NewObjectWithGivenProto(cx, &JSObject::class_, nullptr, cx->global()));
if (!item)
return false;
uint32_t textId = eventItem->textId;
if (textId != TraceLogger_Stop && !logger->textIdIsScriptEvent(textId))
continue;
const char *type = (textId == TraceLogger_Stop) ? "Stop" : "Script";
if (!DefineProperty(cx, item, logTypeId, type, strlen(type)))
return false;
if (textId != TraceLogger_Stop) {
const char *filename, *lineno, *colno;
size_t filename_len, lineno_len, colno_len;
logger->extractScriptDetails(textId, &filename, &filename_len, &lineno, &lineno_len,
&colno, &colno_len);
if (!DefineProperty(cx, item, fileNameId, filename, filename_len))
return false;
if (!DefineProperty(cx, item, lineNumberId, lineno, lineno_len))
return false;
if (!DefineProperty(cx, item, columnNumberId, colno, colno_len))
return false;
}
RootedValue obj(cx, ObjectValue(*item));
if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE))
return false;
index++;
}
/* Add "lostEvents" indicating if there are events that were lost. */
RootedValue lost(cx, BooleanValue(lostEvents));
if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE))
return false;
args.rval().setObject(*array);
return true;
}
const JSPropertySpec Debugger::properties[] = { const JSPropertySpec Debugger::properties[] = {
JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0), JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0),
JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement, JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement,
@ -3868,7 +4048,14 @@ const JSFunctionSpec Debugger::methods[] = {
JS_FN("findObjects", Debugger::findObjects, 1, 0), JS_FN("findObjects", Debugger::findObjects, 1, 0),
JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0), JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0),
JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0), JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0),
JS_FN("setupTraceLoggerScriptCalls", Debugger::setupTraceLoggerScriptCalls, 0, 0),
JS_FN("drainTraceLoggerScriptCalls", Debugger::drainTraceLoggerScriptCalls, 0, 0),
JS_FN("startTraceLogger", Debugger::startTraceLogger, 0, 0),
JS_FN("endTraceLogger", Debugger::endTraceLogger, 0, 0),
#ifdef NIGHTLY_BUILD
JS_FN("setupTraceLogger", Debugger::setupTraceLogger, 1, 0), JS_FN("setupTraceLogger", Debugger::setupTraceLogger, 1, 0),
JS_FN("drainTraceLogger", Debugger::drainTraceLogger, 0, 0),
#endif
JS_FS_END JS_FS_END
}; };

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

@ -296,6 +296,17 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
/* The map from debuggee Envs to Debugger.Environment instances. */ /* The map from debuggee Envs to Debugger.Environment instances. */
ObjectWeakMap environments; ObjectWeakMap environments;
/*
* Keep track of tracelogger last drained identifiers to know if there are
* lost events.
*/
#ifdef NIGHTLY_BUILD
uint32_t traceLoggerLastDrainedId;
uint32_t traceLoggerLastDrainedIteration;
#endif
uint32_t traceLoggerScriptedCallsLastDrainedId;
uint32_t traceLoggerScriptedCallsLastDrainedIteration;
class FrameRange; class FrameRange;
class ScriptQuery; class ScriptQuery;
class ObjectQuery; class ObjectQuery;
@ -394,7 +405,14 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
static bool findObjects(JSContext *cx, unsigned argc, Value *vp); static bool findObjects(JSContext *cx, unsigned argc, Value *vp);
static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp); static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp);
static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp); static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp);
static bool setupTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp);
static bool drainTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp);
static bool startTraceLogger(JSContext *cx, unsigned argc, Value *vp);
static bool endTraceLogger(JSContext *cx, unsigned argc, Value *vp);
#ifdef NIGHTLY_BUILD
static bool setupTraceLogger(JSContext *cx, unsigned argc, Value *vp); static bool setupTraceLogger(JSContext *cx, unsigned argc, Value *vp);
static bool drainTraceLogger(JSContext *cx, unsigned argc, Value *vp);
#endif
static bool construct(JSContext *cx, unsigned argc, Value *vp); static bool construct(JSContext *cx, unsigned argc, Value *vp);
static const JSPropertySpec properties[]; static const JSPropertySpec properties[];
static const JSFunctionSpec methods[]; static const JSFunctionSpec methods[];

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

@ -97,6 +97,7 @@ TraceLoggerThread::TraceLoggerThread()
: enabled(0), : enabled(0),
failed(false), failed(false),
graph(), graph(),
iteration_(0),
top(nullptr) top(nullptr)
{ } { }
@ -252,12 +253,58 @@ TraceLoggerThread::disable()
} }
const char * const char *
TraceLoggerThread::eventText(uint32_t id) { TraceLoggerThread::eventText(uint32_t id)
{
if (id < TraceLogger_Last) if (id < TraceLogger_Last)
return TLTextIdString(static_cast<TraceLoggerTextId>(id)); return TLTextIdString(static_cast<TraceLoggerTextId>(id));
return extraTextId[id - TraceLogger_Last]; return extraTextId[id - TraceLogger_Last];
} }
bool
TraceLoggerThread::textIdIsScriptEvent(uint32_t id)
{
if (id < TraceLogger_Last)
return false;
// Currently this works by checking if text begins with "script".
const char *str = eventText(id);
return EqualChars(str, "script", 6);
}
void
TraceLoggerThread::extractScriptDetails(uint32_t textId, const char **filename, size_t *filename_len,
const char **lineno, size_t *lineno_len, const char **colno,
size_t *colno_len)
{
MOZ_ASSERT(textIdIsScriptEvent(textId));
const char *script = eventText(textId);
// Get the start of filename (remove 'script ' at the start).
MOZ_ASSERT(EqualChars(script, "script ", 7));
*filename = script + 7;
// Get the start of lineno and colno.
*lineno = script;
*colno = script;
const char *next = script - 1;
while ((next = strchr(next + 1, ':'))) {
*lineno = *colno;
*colno = next;
}
MOZ_ASSERT(*lineno && *lineno != script);
MOZ_ASSERT(*colno && *colno != script);
// Remove the ':' at the front.
*lineno = *lineno + 1;
*colno = *colno + 1;
*filename_len = *lineno - *filename - 1;
*lineno_len = *colno - *lineno - 1;
*colno_len = strlen(*colno);
}
uint32_t uint32_t
TraceLoggerThread::createTextId(const char *text) TraceLoggerThread::createTextId(const char *text)
{ {
@ -381,6 +428,7 @@ TraceLoggerThread::logTimestamp(uint32_t id)
if (graph.get()) if (graph.get())
graph->log(events); graph->log(events);
iteration_++;
events.clear(); events.clear();
// Log the time it took to flush the events as being from the // Log the time it took to flush the events as being from the

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

@ -85,6 +85,10 @@ class TraceLoggerThread
ContinuousSpace<EventEntry> events; ContinuousSpace<EventEntry> events;
// Every time the events get flushed, this count is increased by one.
// This together with events.lastEntryId(), gives an unique position.
uint32_t iteration_;
public: public:
AutoTraceLog *top; AutoTraceLog *top;
@ -99,7 +103,48 @@ class TraceLoggerThread
bool enable(JSContext *cx); bool enable(JSContext *cx);
bool disable(); bool disable();
// Given the previous iteration and lastEntryId, return an array of events
// (there could be lost events). At the same time update the iteration and
// lastEntry and gives back how many events there are.
EventEntry *getEventsStartingAt(uint32_t *lastIteration, uint32_t *lastEntryId, size_t *num) {
EventEntry *start;
if (iteration_ == *lastIteration) {
MOZ_ASSERT(events.lastEntryId() >= *lastEntryId);
*num = events.lastEntryId() - *lastEntryId;
start = events.data() + *lastEntryId + 1;
} else {
*num = events.lastEntryId() + 1;
start = events.data();
}
*lastIteration = iteration_;
*lastEntryId = events.lastEntryId();
return start;
}
// Extract the details filename, lineNumber and columnNumber out of a event
// containing script information.
void extractScriptDetails(uint32_t textId, const char **filename, size_t *filename_len,
const char **lineno, size_t *lineno_len, const char **colno,
size_t *colno_len);
bool lostEvents(uint32_t lastIteration, uint32_t lastEntryId) {
// If still logging in the same iteration, there are no lost events.
if (lastIteration == iteration_) {
MOZ_ASSERT(lastEntryId <= events.lastEntryId());
return false;
}
// When proceeded to the next iteration and lastEntryId points to
// the maximum capacity there are no logs that are lost.
if (lastIteration + 1 == iteration_ && lastEntryId == events.capacity())
return false;
return true;
}
const char *eventText(uint32_t id); const char *eventText(uint32_t id);
bool textIdIsScriptEvent(uint32_t id);
// The createTextId functions map a unique input to a logger ID. // The createTextId functions map a unique input to a logger ID.
// This can be used to give start and stop events. Calls to these functions should be // This can be used to give start and stop events. Calls to these functions should be