Provide stdout and stderr output in the Observatory debugger.

Implement two new streams, 'Stdout' and 'Stderr' in the service protocol.

BUG=
R=johnmccutchan@google.com

Review URL: https://codereview.chromium.org//1232193003 .
This commit is contained in:
Todd Turnidge 2015-07-14 12:54:07 -07:00
parent a02c7694c5
commit e4684c7627
20 changed files with 685 additions and 131 deletions

View file

@ -7,6 +7,7 @@
#include <string.h>
#include "include/dart_api.h"
#include "include/dart_tools_api.h"
#include "platform/assert.h"
@ -119,6 +120,9 @@ const uint8_t* Builtin::NativeSymbol(Dart_NativeFunction nf) {
}
extern bool capture_stdout;
// Implementation of native functions which are used for some
// test/debug functionality in standalone dart mode.
void FUNCTION_NAME(Builtin_PrintString)(Dart_NativeArguments args) {
@ -126,18 +130,19 @@ void FUNCTION_NAME(Builtin_PrintString)(Dart_NativeArguments args) {
uint8_t* chars = NULL;
Dart_Handle str = Dart_GetNativeArgument(args, 0);
Dart_Handle result = Dart_StringToUTF8(str, &chars, &length);
if (Dart_IsError(result)) {
// TODO(turnidge): Consider propagating some errors here. What if
// an isolate gets interrupted by the embedder in the middle of
// Dart_StringToUTF8? We need to make sure not to swallow the
// interrupt.
fprintf(stdout, "%s\n", Dart_GetError(result));
} else {
// Uses fwrite to support printing NUL bytes.
fwrite(chars, 1, length, stdout);
fputs("\n", stdout);
}
if (Dart_IsError(result)) Dart_PropagateError(result);
// Uses fwrite to support printing NUL bytes.
fwrite(chars, 1, length, stdout);
fputs("\n", stdout);
fflush(stdout);
if (capture_stdout) {
// For now we report print output on the Stdout stream.
uint8_t newline[] = { '\n' };
Dart_ServiceSendDataEvent("Stdout", "WriteEvent", chars, length);
Dart_ServiceSendDataEvent("Stdout", "WriteEvent",
newline, sizeof(newline));
}
}
} // namespace bin

View file

@ -10,12 +10,22 @@
#include "bin/utils.h"
#include "include/dart_api.h"
#include "include/dart_tools_api.h"
namespace dart {
namespace bin {
static const int kMSPerSecond = 1000;
// Are we capturing output from either stdout or stderr for the VM Service?
bool capture_stdio = false;
// Are we capturing output from stdout for the VM service?
bool capture_stdout = false;
// Are we capturing output from stderr for the VM service?
bool capture_stderr = false;
// The file pointer has been passed into Dart as an intptr_t and it is safe
// to pull it out of Dart as a 64-bit integer, cast it to an intptr_t and
@ -52,6 +62,18 @@ bool File::WriteFully(const void* buffer, int64_t num_bytes) {
remaining -= bytes_written; // Reduce the number of remaining bytes.
current_buffer += bytes_written; // Move the buffer forward.
}
if (capture_stdio) {
intptr_t fd = GetFD();
if (fd == STDOUT_FILENO && capture_stdout) {
Dart_ServiceSendDataEvent("Stdout", "WriteEvent",
reinterpret_cast<const uint8_t*>(buffer),
num_bytes);
} else if (fd == STDERR_FILENO && capture_stderr) {
Dart_ServiceSendDataEvent("Stderr", "WriteEvent",
reinterpret_cast<const uint8_t*>(buffer),
num_bytes);
}
}
return true;
}
@ -581,7 +603,7 @@ void FUNCTION_NAME(File_ResolveSymbolicLinks)(Dart_NativeArguments args) {
void FUNCTION_NAME(File_OpenStdio)(Dart_NativeArguments args) {
int64_t fd = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 0));
ASSERT(fd == 0 || fd == 1 || fd == 2);
ASSERT(fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO);
File* file = File::OpenStdio(static_cast<int>(fd));
Dart_SetReturnValue(args, Dart_NewInteger(reinterpret_cast<intptr_t>(file)));
}
@ -589,7 +611,7 @@ void FUNCTION_NAME(File_OpenStdio)(Dart_NativeArguments args) {
void FUNCTION_NAME(File_GetStdioHandleType)(Dart_NativeArguments args) {
int64_t fd = DartUtils::GetIntegerValue(Dart_GetNativeArgument(args, 0));
ASSERT(fd == 0 || fd == 1 || fd == 2);
ASSERT(fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO);
File::StdioHandleType type = File::GetStdioHandleType(static_cast<int>(fd));
Dart_SetReturnValue(args, Dart_NewInteger(type));
}

View file

@ -859,6 +859,37 @@ static const char* ServiceGetIOHandler(
}
extern bool capture_stdio;
extern bool capture_stdout;
extern bool capture_stderr;
static const char* kStdoutStreamId = "Stdout";
static const char* kStderrStreamId = "Stderr";
static bool ServiceStreamListenCallback(const char* stream_id) {
if (strcmp(stream_id, kStdoutStreamId) == 0) {
capture_stdio = true;
capture_stdout = true;
return true;
} else if (strcmp(stream_id, kStderrStreamId) == 0) {
capture_stdio = true;
capture_stderr = true;
return true;
}
return false;
}
static void ServiceStreamCancelCallback(const char* stream_id) {
if (strcmp(stream_id, kStdoutStreamId) == 0) {
capture_stdout = false;
} else if (strcmp(stream_id, kStderrStreamId) == 0) {
capture_stderr = false;
}
capture_stdio = (capture_stdout || capture_stderr);
}
void main(int argc, char** argv) {
char* script_name;
const int EXTRA_VM_ARGUMENTS = 2;
@ -947,6 +978,8 @@ void main(int argc, char** argv) {
Dart_RegisterIsolateServiceRequestCallback(
"getIO", &ServiceGetIOHandler, NULL);
Dart_SetServiceStreamCallbacks(&ServiceStreamListenCallback,
&ServiceStreamCancelCallback);
// Call CreateIsolateAndSetup which creates an isolate and loads up
// the specified application script.

View file

@ -805,6 +805,74 @@ DART_EXPORT void Dart_RegisterRootServiceRequestCallback(
void* user_data);
/*
* ========
* Event Streams
* ========
*/
/**
* A callback invoked when the VM service gets a request to listen to
* some stream.
*
* \return Returns true iff the embedder supports the named stream id.
*/
typedef bool (*Dart_ServiceStreamListenCallback)(const char* stream_id);
/**
* A callback invoked when the VM service gets a request to cancel
* some stream.
*/
typedef void (*Dart_ServiceStreamCancelCallback)(const char* stream_id);
/**
* Adds VM service stream callbacks.
*
* \param listen_callback A function pointer to a listen callback function.
* A listen callback function should not be already set when this function
* is called. A NULL value removes the existing listen callback function
* if any.
*
* \param cancel_callback A function pointer to a cancel callback function.
* A cancel callback function should not be already set when this function
* is called. A NULL value removes the existing cancel callback function
* if any.
*
* \return Success if the callbacks were added. Otherwise, returns an
* error handle.
*/
DART_EXPORT Dart_Handle Dart_SetServiceStreamCallbacks(
Dart_ServiceStreamListenCallback listen_callback,
Dart_ServiceStreamCancelCallback cancel_callback);
/**
* Sends a data event to clients of the VM Service.
*
* A data event is used to pass an array of bytes to subscribed VM
* Service clients. For example, in the standalone embedder, this is
* function used to provide WriteEvents on the Stdout and Stderr
* streams.
*
* If the embedder passes in a stream id for which no client is
* subscribed, then the event is ignored.
*
* \param stream_id The id of the stream on which to post the event.
*
* \param event_kind A string identifying what kind of event this is.
* For example, 'WriteEvent'.
*
* \param bytes A pointer to an array of bytes.
*
* \param bytes_length The length of the byte array.
*
* \return Success if the arguments are well formed. Otherwise, returns an
* error handle.
*/
DART_EXPORT Dart_Handle Dart_ServiceSendDataEvent(const char* stream_id,
const char* event_kind,
const uint8_t* bytes,
intptr_t bytes_length);
/*
* ========
* Timeline

View file

@ -5,6 +5,7 @@
library service;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:math' as math;

View file

@ -854,6 +854,67 @@ class RefreshCommand extends DebuggerCommand {
'Syntax: refresh <subcommand>\n';
}
class _VMStreamPrinter {
ObservatoryDebugger _debugger;
_VMStreamPrinter(this._debugger);
String _savedStream;
String _savedIsolate;
String _savedLine;
List<String> _buffer = [];
void onEvent(String streamName, ServiceEvent event) {
String isolateName = event.isolate.name;
// If we get a line from a different isolate/stream, flush
// any pending output, even if it is not newline-terminated.
if ((_savedIsolate != null && isolateName != _savedIsolate) ||
(_savedStream != null && streamName != _savedStream)) {
flush();
}
String data = event.bytesAsString;
bool hasNewline = data.endsWith('\n');
if (_savedLine != null) {
data = _savedLine + data;
_savedIsolate = null;
_savedStream = null;
_savedLine = null;
}
var lines = data.split('\n').where((line) => line != '').toList();
if (lines.isEmpty) {
return;
}
int limit = (hasNewline ? lines.length : lines.length - 1);
for (int i = 0; i < limit; i++) {
_buffer.add(_format(isolateName, streamName, lines[i]));
}
// If there is no newline, we save the last line of output for next time.
if (!hasNewline) {
_savedIsolate = isolateName;
_savedStream = streamName;
_savedLine = lines[lines.length - 1];
}
}
void flush() {
// If there is any saved output, flush it now.
if (_savedLine != null) {
_buffer.add(_format(_savedIsolate, _savedStream, _savedLine));
_savedIsolate = null;
_savedStream = null;
_savedLine = null;
}
if (_buffer.isNotEmpty) {
_debugger.console.printStdio(_buffer);
_buffer.clear();
}
}
String _format(String isolateName, String streamName, String line) {
return '${isolateName}:${streamName}> ${line}';
}
}
// Tracks the state for an isolate debugging session.
class ObservatoryDebugger extends Debugger {
RootCommand cmd;
@ -898,6 +959,7 @@ class ObservatoryDebugger extends Debugger {
new IsolateCommand(this),
new RefreshCommand(this),
]);
_stdioPrinter = new _VMStreamPrinter(this);
}
VM get vm => page.app.vm;
@ -997,6 +1059,7 @@ class ObservatoryDebugger extends Debugger {
}
void reportStatus() {
flushStdio();
if (_isolate == null) {
console.print('No current isolate');
} else if (_isolate.idle) {
@ -1116,6 +1179,7 @@ class ObservatoryDebugger extends Debugger {
case ServiceEvent.kPauseException:
if (event.owner == isolate) {
_refreshStack(event).then((_) {
flushStdio();
_reportPause(event);
});
}
@ -1123,6 +1187,7 @@ class ObservatoryDebugger extends Debugger {
case ServiceEvent.kResume:
if (event.owner == isolate) {
flushStdio();
console.print('Continuing...');
}
break;
@ -1147,6 +1212,20 @@ class ObservatoryDebugger extends Debugger {
}
}
_VMStreamPrinter _stdioPrinter;
void flushStdio() {
_stdioPrinter.flush();
}
void onStdout(ServiceEvent event) {
_stdioPrinter.onEvent('stdout', event);
}
void onStderr(ServiceEvent event) {
_stdioPrinter.onEvent('stderr', event);
}
static String _commonPrefix(String a, String b) {
int pos = 0;
while (pos < a.length && pos < b.length) {
@ -1236,6 +1315,8 @@ class DebuggerPageElement extends ObservatoryElement {
Future<StreamSubscription> _isolateSubscriptionFuture;
Future<StreamSubscription> _debugSubscriptionFuture;
Future<StreamSubscription> _stdoutSubscriptionFuture;
Future<StreamSubscription> _stderrSubscriptionFuture;
@override
void attached() {
@ -1269,6 +1350,13 @@ class DebuggerPageElement extends ObservatoryElement {
app.vm.listenEventStream(VM.kIsolateStream, debugger.onEvent);
_debugSubscriptionFuture =
app.vm.listenEventStream(VM.kDebugStream, debugger.onEvent);
_stdoutSubscriptionFuture =
app.vm.listenEventStream(VM.kStdoutStream, debugger.onStdout);
_stderrSubscriptionFuture =
app.vm.listenEventStream(VM.kStderrStream, debugger.onStderr);
// Turn on the periodic poll timer for this page.
pollPeriod = const Duration(milliseconds:100);
onClick.listen((event) {
// Random clicks should focus on the text box. If the user selects
@ -1280,12 +1368,20 @@ class DebuggerPageElement extends ObservatoryElement {
});
}
void onPoll() {
debugger.flushStdio();
}
@override
void detached() {
cancelFutureSubscription(_isolateSubscriptionFuture);
_isolateSubscriptionFuture = null;
cancelFutureSubscription(_debugSubscriptionFuture);
_debugSubscriptionFuture = null;
cancelFutureSubscription(_stdoutSubscriptionFuture);
_stdoutSubscriptionFuture = null;
cancelFutureSubscription(_stderrSubscriptionFuture);
_stderrSubscriptionFuture = null;
super.detached();
}
}
@ -1665,6 +1761,21 @@ class DebuggerConsoleElement extends ObservatoryElement {
span.scrollIntoView();
}
void printStdio(List<String> lines) {
var lastSpan;
for (var line in lines) {
var span = new SpanElement();
span.classes.add('green');
span.appendText(line);
span.appendText('\n');
$['consoleText'].children.add(span);
lastSpan = span;
}
if (lastSpan != null) {
lastSpan.scrollIntoView();
}
}
void printRef(Instance ref, { bool newline:true }) {
var refElement = new Element.tag('instance-ref');
refElement.ref = ref;

View file

@ -420,13 +420,18 @@
line-height: 125%;
color: red;
}
.green {
font: normal 14px consolas, courier, monospace;
white-space: pre;
line-height: 125%;
color: green;
}
.spacer {
height: 20px;
}
</style>
<div id="consoleText" class="console">
<!-- Console output is added programmatically here using the 'normal'
and 'bold' styles. -->
<!-- Console output is added programmatically -->
</div>
</template>
</polymer-element>

View file

@ -697,6 +697,8 @@ abstract class VM extends ServiceObjectOwner {
static const kIsolateStream = 'Isolate';
static const kDebugStream = 'Debug';
static const kGCStream = 'GC';
static const kStdoutStream = 'Stdout';
static const kStderrStream = 'Stderr';
static const _kGraphStream = '_Graph';
/// Returns a single-subscription Stream object for a VM event stream.
@ -1625,6 +1627,7 @@ class ServiceEvent extends ServiceObject {
@observable int count;
@observable String reason;
@observable String exceptions;
@observable String bytesAsString;
int chunkIndex, chunkCount, nodeCount;
@observable bool get isPauseEvent {
@ -1680,6 +1683,10 @@ class ServiceEvent extends ServiceObject {
map['_debuggerSettings']['_exceptions'] != null) {
exceptions = map['_debuggerSettings']['_exceptions'];
}
if (map['bytes'] != null) {
var bytes = decodeBase64(map['bytes']);
bytesAsString = UTF8.decode(bytes);
}
}
String toString() {

View file

@ -0,0 +1,89 @@
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// VMOptions=--compile-all --error_on_bad_type --error_on_bad_override
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:observatory/service_io.dart';
import 'package:unittest/unittest.dart';
import 'test_helper.dart';
void test() {
debugger();
stdout.write('stdout');
debugger();
print('print');
debugger();
stderr.write('stderr');
}
var tests = [
hasStoppedAtBreakpoint,
(Isolate isolate) async {
Completer completer = new Completer();
var stdoutSub;
stdoutSub = await isolate.vm.listenEventStream(
VM.kStdoutStream,
(ServiceEvent event) {
expect(event.kind, equals('WriteEvent'));
expect(event.bytesAsString, equals('stdout'));
cancelFutureSubscription(stdoutSub).then((_) {
completer.complete();
});
});
await isolate.resume();
await completer.future;
},
hasStoppedAtBreakpoint,
(Isolate isolate) async {
Completer completer = new Completer();
var stdoutSub;
int eventNumber = 1;
stdoutSub = await isolate.vm.listenEventStream(
VM.kStdoutStream,
(ServiceEvent event) {
expect(event.kind, equals('WriteEvent'));
if (eventNumber == 1) {
expect(event.bytesAsString, equals('print'));
} else if (eventNumber == 2) {
expect(event.bytesAsString, equals('\n'));
cancelFutureSubscription(stdoutSub).then((_) {
completer.complete();
});
} else {
expect(true, false);
}
eventNumber++;
});
await isolate.resume();
await completer.future;
},
hasStoppedAtBreakpoint,
(Isolate isolate) async {
Completer completer = new Completer();
var stderrSub;
stderrSub = await isolate.vm.listenEventStream(
VM.kStderrStream,
(ServiceEvent event) {
expect(event.kind, equals('WriteEvent'));
expect(event.bytesAsString, equals('stderr'));
cancelFutureSubscription(stderrSub).then((_) {
completer.complete();
});
});
await isolate.resume();
await completer.future;
},
];
main(args) => runIsolateTests(args, tests, testeeConcurrent: test);

View file

@ -5692,6 +5692,67 @@ DART_EXPORT void Dart_RegisterRootServiceRequestCallback(
}
DART_EXPORT Dart_Handle Dart_SetServiceStreamCallbacks(
Dart_ServiceStreamListenCallback listen_callback,
Dart_ServiceStreamCancelCallback cancel_callback) {
if (listen_callback != NULL) {
if (Service::stream_listen_callback() != NULL) {
return Api::NewError(
"%s permits only one listen callback to be registered, please "
"remove the existing callback and then add this callback",
CURRENT_FUNC);
}
} else {
if (Service::stream_listen_callback() == NULL) {
return Api::NewError(
"%s expects 'listen_callback' to be present in the callback set.",
CURRENT_FUNC);
}
}
if (cancel_callback != NULL) {
if (Service::stream_cancel_callback() != NULL) {
return Api::NewError(
"%s permits only one cancel callback to be registered, please "
"remove the existing callback and then add this callback",
CURRENT_FUNC);
}
} else {
if (Service::stream_cancel_callback() == NULL) {
return Api::NewError(
"%s expects 'cancel_callback' to be present in the callback set.",
CURRENT_FUNC);
}
}
Service::SetEmbedderStreamCallbacks(listen_callback, cancel_callback);
return Api::Success();
}
DART_EXPORT Dart_Handle Dart_ServiceSendDataEvent(const char* stream_id,
const char* event_kind,
const uint8_t* bytes,
intptr_t bytes_length) {
Isolate* isolate = Isolate::Current();
DARTSCOPE(isolate);
if (stream_id == NULL) {
RETURN_NULL_ERROR(stream_id);
}
if (event_kind == NULL) {
RETURN_NULL_ERROR(event_kind);
}
if (bytes == NULL) {
RETURN_NULL_ERROR(bytes);
}
if (bytes_length < 0) {
return Api::NewError("%s expects argument 'bytes_length' to be >= 0.",
CURRENT_FUNC);
}
Service::SendEmbedderEvent(isolate, stream_id, event_kind,
bytes, bytes_length);
return Api::Success();
}
DART_EXPORT Dart_Handle Dart_TimelineDuration(const char* label,
int64_t start_micros,
int64_t end_micros) {

View file

@ -236,8 +236,8 @@ ActivationFrame::ActivationFrame(
bool Debugger::HasEventHandler() {
return ((event_handler_ != NULL) ||
Service::NeedsIsolateEvents() ||
Service::NeedsDebugEvents());
Service::isolate_stream.enabled() ||
Service::debug_stream.enabled());
}
@ -251,11 +251,11 @@ static bool ServiceNeedsDebuggerEvent(DebuggerEvent::EventType type) {
case DebuggerEvent::kBreakpointReached:
case DebuggerEvent::kExceptionThrown:
case DebuggerEvent::kIsolateInterrupted:
return Service::NeedsDebugEvents();
return Service::debug_stream.enabled();
case DebuggerEvent::kIsolateCreated:
case DebuggerEvent::kIsolateShutdown:
return Service::NeedsIsolateEvents();
return Service::isolate_stream.enabled();
default:
UNREACHABLE();
@ -324,10 +324,10 @@ void Debugger::SignalIsolateInterrupted() {
// The vm service handles breakpoint notifications in a different way
// than the regular debugger breakpoint notifications.
static void SendServiceBreakpointEvent(ServiceEvent::EventType type,
static void SendServiceBreakpointEvent(ServiceEvent::EventKind kind,
Breakpoint* bpt) {
if (Service::NeedsDebugEvents()) {
ServiceEvent service_event(Isolate::Current(), type);
if (Service::debug_stream.enabled()) {
ServiceEvent service_event(Isolate::Current(), kind);
service_event.set_breakpoint(bpt);
Service::HandleEvent(&service_event);
}

View file

@ -680,7 +680,7 @@ void Heap::RecordAfterGC() {
stats_.after_.old_ = old_space_->GetCurrentUsage();
ASSERT(gc_in_progress_);
gc_in_progress_ = false;
if (Service::NeedsGCEvents()) {
if (Service::gc_stream.enabled()) {
ServiceEvent event(Isolate::Current(), ServiceEvent::kGC);
event.set_gc_stats(&stats_);
Service::HandleEvent(&event);

View file

@ -485,7 +485,7 @@ bool IsolateMessageHandler::HandleMessage(Message* message) {
void IsolateMessageHandler::NotifyPauseOnStart() {
if (Service::NeedsDebugEvents()) {
if (Service::debug_stream.enabled()) {
StartIsolateScope start_isolate(isolate());
StackZone zone(I);
HandleScope handle_scope(I);
@ -496,7 +496,7 @@ void IsolateMessageHandler::NotifyPauseOnStart() {
void IsolateMessageHandler::NotifyPauseOnExit() {
if (Service::NeedsDebugEvents()) {
if (Service::debug_stream.enabled()) {
StartIsolateScope start_isolate(isolate());
StackZone zone(I);
HandleScope handle_scope(I);

View file

@ -5,6 +5,7 @@
#include "vm/service.h"
#include "include/dart_api.h"
#include "include/dart_native_api.h"
#include "platform/globals.h"
#include "vm/compiler.h"
@ -74,32 +75,46 @@ EmbedderServiceHandler* Service::root_service_handler_head_ = NULL;
struct ServiceMethodDescriptor;
ServiceMethodDescriptor* FindMethod(const char* method_name);
// TODO(turnidge): Build a general framework later. For now, we have
// a small set of well-known streams.
bool Service::needs_isolate_events_ = false;
bool Service::needs_debug_events_ = false;
bool Service::needs_gc_events_ = false;
bool Service::needs_echo_events_ = false;
bool Service::needs_graph_events_ = false;
void Service::ListenStream(const char* stream_id) {
// Support for streams defined in embedders.
Dart_ServiceStreamListenCallback Service::stream_listen_callback_ = NULL;
Dart_ServiceStreamCancelCallback Service::stream_cancel_callback_ = NULL;
// These are the set of streams known to the core VM.
StreamInfo Service::isolate_stream("Isolate");
StreamInfo Service::debug_stream("Debug");
StreamInfo Service::gc_stream("GC");
StreamInfo Service::echo_stream("_Echo");
StreamInfo Service::graph_stream("_Graph");
static StreamInfo* streams_[] = {
&Service::isolate_stream,
&Service::debug_stream,
&Service::gc_stream,
&Service::echo_stream,
&Service::graph_stream,
};
bool Service::ListenStream(const char* stream_id) {
if (FLAG_trace_service) {
OS::Print("vm-service: starting stream '%s'\n",
stream_id);
}
if (strcmp(stream_id, "Isolate") == 0) {
needs_isolate_events_ = true;
} else if (strcmp(stream_id, "Debug") == 0) {
needs_debug_events_ = true;
} else if (strcmp(stream_id, "GC") == 0) {
needs_gc_events_ = true;
} else if (strcmp(stream_id, "_Echo") == 0) {
needs_echo_events_ = true;
} else if (strcmp(stream_id, "_Graph") == 0) {
needs_graph_events_ = true;
} else {
UNREACHABLE();
intptr_t num_streams = sizeof(streams_) /
sizeof(streams_[0]);
for (intptr_t i = 0; i < num_streams; i++) {
if (strcmp(stream_id, streams_[i]->id()) == 0) {
streams_[i]->set_enabled(true);
return true;
}
}
if (stream_listen_callback_) {
return (*stream_listen_callback_)(stream_id);
}
return false;
}
void Service::CancelStream(const char* stream_id) {
@ -107,18 +122,16 @@ void Service::CancelStream(const char* stream_id) {
OS::Print("vm-service: stopping stream '%s'\n",
stream_id);
}
if (strcmp(stream_id, "Isolate") == 0) {
needs_isolate_events_ = false;
} else if (strcmp(stream_id, "Debug") == 0) {
needs_debug_events_ = false;
} else if (strcmp(stream_id, "GC") == 0) {
needs_gc_events_ = false;
} else if (strcmp(stream_id, "_Echo") == 0) {
needs_echo_events_ = false;
} else if (strcmp(stream_id, "_Graph") == 0) {
needs_graph_events_ = false;
} else {
UNREACHABLE();
intptr_t num_streams = sizeof(streams_) /
sizeof(streams_[0]);
for (intptr_t i = 0; i < num_streams; i++) {
if (strcmp(stream_id, streams_[i]->id()) == 0) {
streams_[i]->set_enabled(false);
return;
}
}
if (stream_cancel_callback_) {
return (*stream_cancel_callback_)(stream_id);
}
}
@ -623,6 +636,7 @@ void Service::SendEvent(const char* stream_id,
}
// TODO(turnidge): Rewrite this method to use Post_CObject instead.
void Service::SendEventWithData(const char* stream_id,
const char* event_type,
const String& meta,
@ -658,6 +672,9 @@ void Service::HandleEvent(ServiceEvent* event) {
if (ServiceIsolate::IsServiceIsolateDescendant(event->isolate())) {
return;
}
if (!ServiceIsolate::IsRunning()) {
return;
}
JSONStream js;
const char* stream_id = event->stream_id();
ASSERT(stream_id != NULL);
@ -666,9 +683,35 @@ void Service::HandleEvent(ServiceEvent* event) {
jsobj.AddProperty("event", event);
jsobj.AddProperty("streamId", stream_id);
}
const String& message = String::Handle(String::New(js.ToCString()));
SendEvent(stream_id, ServiceEvent::EventTypeToCString(event->type()),
message);
// Message is of the format [<stream id>, <json string>].
//
// Build the event message in the C heap to avoid dart heap
// allocation. This method can be called while we have acquired a
// direct pointer to typed data, so we can't allocate here.
Dart_CObject list_cobj;
Dart_CObject* list_values[2];
list_cobj.type = Dart_CObject_kArray;
list_cobj.value.as_array.length = 2;
list_cobj.value.as_array.values = list_values;
Dart_CObject stream_id_cobj;
stream_id_cobj.type = Dart_CObject_kString;
stream_id_cobj.value.as_string = const_cast<char*>(stream_id);
list_values[0] = &stream_id_cobj;
Dart_CObject json_cobj;
json_cobj.type = Dart_CObject_kString;
json_cobj.value.as_string = const_cast<char*>(js.ToCString());
list_values[1] = &json_cobj;
if (FLAG_trace_service) {
OS::Print(
"vm-service: Pushing event of type %s to stream %s\n",
event->KindAsCString(), stream_id);
}
Dart_PostCObject(ServiceIsolate::Port(), &list_cobj);
}
@ -792,6 +835,14 @@ void Service::RegisterRootEmbedderCallback(
}
void Service::SetEmbedderStreamCallbacks(
Dart_ServiceStreamListenCallback listen_callback,
Dart_ServiceStreamCancelCallback cancel_callback) {
stream_listen_callback_ = listen_callback;
stream_cancel_callback_ = cancel_callback;
}
EmbedderServiceHandler* Service::FindRootEmbedderHandler(
const char* name) {
EmbedderServiceHandler* current = root_service_handler_head_;
@ -875,16 +926,16 @@ void Service::SendEchoEvent(Isolate* isolate, const char* text) {
event.AddProperty("text", text);
}
}
jsobj.AddProperty("streamId", "_Echo");
jsobj.AddProperty("streamId", echo_stream.id());
}
const String& message = String::Handle(String::New(js.ToCString()));
uint8_t data[] = {0, 128, 255};
SendEventWithData("_Echo", "_Echo", message, data, sizeof(data));
SendEventWithData(echo_stream.id(), "_Echo", message, data, sizeof(data));
}
static bool TriggerEchoEvent(Isolate* isolate, JSONStream* js) {
if (Service::NeedsEchoEvents()) {
if (Service::echo_stream.enabled()) {
Service::SendEchoEvent(isolate, js->LookupParam("text"));
}
JSONObject jsobj(js);
@ -2190,7 +2241,7 @@ static bool Resume(Isolate* isolate, JSONStream* js) {
const char* step_param = js->LookupParam("step");
if (isolate->message_handler()->paused_on_start()) {
isolate->message_handler()->set_pause_on_start(false);
if (Service::NeedsDebugEvents()) {
if (Service::debug_stream.enabled()) {
ServiceEvent event(isolate, ServiceEvent::kResume);
Service::HandleEvent(&event);
}
@ -2384,7 +2435,7 @@ static const MethodParameter* request_heap_snapshot_params[] = {
static bool RequestHeapSnapshot(Isolate* isolate, JSONStream* js) {
if (Service::NeedsGraphEvents()) {
if (Service::graph_stream.enabled()) {
Service::SendGraphEvent(isolate);
}
// TODO(koda): Provide some id that ties this request to async response(s).
@ -2419,7 +2470,7 @@ void Service::SendGraphEvent(Isolate* isolate) {
event.AddProperty("chunkCount", num_chunks);
event.AddProperty("nodeCount", node_count);
}
jsobj.AddProperty("streamId", "_Graph");
jsobj.AddProperty("streamId", graph_stream.id());
}
const String& message = String::Handle(String::New(js.ToCString()));
@ -2429,13 +2480,14 @@ void Service::SendGraphEvent(Isolate* isolate) {
? stream.bytes_written() - (i * kChunkSize)
: kChunkSize;
SendEventWithData("_Graph", "_Graph", message, chunk_start, chunk_size);
SendEventWithData(graph_stream.id(), "_Graph", message,
chunk_start, chunk_size);
}
}
void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
if (!Service::NeedsDebugEvents()) {
if (!Service::debug_stream.enabled()) {
return;
}
ServiceEvent event(isolate, ServiceEvent::kInspect);
@ -2444,6 +2496,22 @@ void Service::SendInspectEvent(Isolate* isolate, const Object& inspectee) {
}
void Service::SendEmbedderEvent(Isolate* isolate,
const char* stream_id,
const char* event_kind,
const uint8_t* bytes,
intptr_t bytes_len) {
if (!Service::debug_stream.enabled()) {
return;
}
ServiceEvent event(isolate, ServiceEvent::kEmbedder);
event.set_embedder_kind(event_kind);
event.set_embedder_stream_id(stream_id);
event.set_bytes(bytes, bytes_len);
Service::HandleEvent(&event);
}
class ContainsAddressVisitor : public FindObjectVisitor {
public:
ContainsAddressVisitor(Isolate* isolate, uword addr)
@ -2726,7 +2794,7 @@ static bool SetExceptionPauseInfo(Isolate* isolate, JSONStream* js) {
}
isolate->debugger()->SetExceptionPauseInfo(info);
if (Service::NeedsDebugEvents()) {
if (Service::debug_stream.enabled()) {
ServiceEvent event(isolate, ServiceEvent::kDebuggerSettingsUpdate);
Service::HandleEvent(&event);
}
@ -2812,7 +2880,7 @@ static const MethodParameter* set_name_params[] = {
static bool SetName(Isolate* isolate, JSONStream* js) {
isolate->set_debugger_name(js->LookupParam("name"));
if (Service::NeedsIsolateEvents()) {
if (Service::isolate_stream.enabled()) {
ServiceEvent event(isolate, ServiceEvent::kIsolateUpdate);
Service::HandleEvent(&event);
}

View file

@ -59,6 +59,21 @@ class RingServiceIdZone : public ServiceIdZone {
};
class StreamInfo {
public:
explicit StreamInfo(const char* id) : id_(id), enabled_(false) {}
const char* id() { return id_; }
void set_enabled(bool value) { enabled_ = value; }
bool enabled() { return enabled_; }
private:
const char* id_;
bool enabled_;
};
class Service : public AllStatic {
public:
// Handles a message which is not directed to an isolate.
@ -67,15 +82,6 @@ class Service : public AllStatic {
// Handles a message which is directed to a particular isolate.
static void HandleIsolateMessage(Isolate* isolate, const Array& message);
static bool NeedsIsolateEvents() { return needs_isolate_events_; }
static bool NeedsDebugEvents() { return needs_debug_events_; }
static bool NeedsGCEvents() { return needs_gc_events_; }
static bool NeedsEchoEvents() { return needs_echo_events_; }
static bool NeedsGraphEvents() { return needs_graph_events_; }
static void ListenStream(const char* stream_id);
static void CancelStream(const char* stream_id);
static void HandleEvent(ServiceEvent* event);
static void RegisterIsolateEmbedderCallback(
@ -88,10 +94,37 @@ class Service : public AllStatic {
Dart_ServiceRequestCallback callback,
void* user_data);
static void SetEmbedderStreamCallbacks(
Dart_ServiceStreamListenCallback listen_callback,
Dart_ServiceStreamCancelCallback cancel_callback);
static void SendEchoEvent(Isolate* isolate, const char* text);
static void SendGraphEvent(Isolate* isolate);
static void SendInspectEvent(Isolate* isolate, const Object& inspectee);
static void SendEmbedderEvent(Isolate* isolate,
const char* stream_id,
const char* event_kind,
const uint8_t* bytes,
intptr_t bytes_len);
// Well-known streams.
static StreamInfo isolate_stream;
static StreamInfo debug_stream;
static StreamInfo gc_stream;
static StreamInfo echo_stream;
static StreamInfo graph_stream;
static bool ListenStream(const char* stream_id);
static void CancelStream(const char* stream_id);
static Dart_ServiceStreamListenCallback stream_listen_callback() {
return stream_listen_callback_;
}
static Dart_ServiceStreamCancelCallback stream_cancel_callback() {
return stream_cancel_callback_;
}
private:
static void InvokeMethod(Isolate* isolate, const Array& message);
@ -113,6 +146,8 @@ class Service : public AllStatic {
static EmbedderServiceHandler* isolate_service_handler_head_;
static EmbedderServiceHandler* root_service_handler_head_;
static Dart_ServiceStreamListenCallback stream_listen_callback_;
static Dart_ServiceStreamCancelCallback stream_cancel_callback_;
static bool needs_isolate_events_;
static bool needs_debug_events_;

View file

@ -620,6 +620,15 @@ Isolate | IsolateStart, IsolateExit, IsolateUpdate
Debug | PauseStart, PauseExit, PauseBreakpoint, PauseInterrupted, PauseException, Resume, BreakpointAdded, BreakpointResolved, BreakpointRemoved, Inspect
GC | GC
Additionally, some embedders provide the _Stdout_ and _Stderr_
streams. These streams allow the client to subscribe to writes to
stdout and stderr.
streamId | event types provided
-------- | -----------
Stdout | WriteEvent
Stderr | WriteEvent
It is considered a _backwards compatible_ change to add a new type of event to an existing stream.
Clients should be written to handle this gracefully, perhaps by warning and ignoring.
@ -992,6 +1001,11 @@ class Event extends Response {
// The exception associated with this event, if this is a
// PauseException event.
@Instance exception [optional];
// An array of bytes, encoded as a base64 string.
//
// This is provided for the WriteEvent event.
string bytes [optional];
}
```
@ -1044,7 +1058,10 @@ enum EventKind {
BreakpointRemoved,
// A garbage collection event.
GC
GC,
// Notification of bytes written, for example, to stdout/stderr.
WriteEvent
}
```

View file

@ -178,11 +178,6 @@ class VMService extends MessageRouter {
return JSON.encode(response);
}
bool _isValidStream(String streamId) {
final validStreams = [ 'Isolate', 'Debug', 'GC', '_Echo', '_Graph' ];
return validStreams.contains(streamId);
}
bool _isAnyClientSubscribed(String streamId) {
for (var client in clients) {
if (client.streams.contains(streamId)) {
@ -196,16 +191,15 @@ class VMService extends MessageRouter {
var client = message.client;
var streamId = message.params['streamId'];
if (!_isValidStream(streamId)) {
return _encodeError(
message, _kInvalidParams,
details:"streamListen: invalid 'streamId' parameter: ${streamId}");
}
if (client.streams.contains(streamId)) {
return _encodeError(message, _kStreamAlreadySubscribed);
}
if (!_isAnyClientSubscribed(streamId)) {
_vmListenStream(streamId);
if (!_vmListenStream(streamId)) {
return _encodeError(
message, _kInvalidParams,
details:"streamListen: invalid 'streamId' parameter: ${streamId}");
}
}
client.streams.add(streamId);
@ -217,11 +211,6 @@ class VMService extends MessageRouter {
var client = message.client;
var streamId = message.params['streamId'];
if (!_isValidStream(streamId)) {
return _encodeError(
message, _kInvalidParams,
details:"streamCancel: invalid 'streamId' parameter: ${streamId}");
}
if (!client.streams.contains(streamId)) {
return _encodeError(message, _kStreamNotSubscribed);
}
@ -331,6 +320,6 @@ void _onStart() native "VMService_OnStart";
void _onExit() native "VMService_OnExit";
void _vmListenStream(String streamId) native "VMService_ListenStream";
bool _vmListenStream(String streamId) native "VMService_ListenStream";
void _vmCancelStream(String streamId) native "VMService_CancelStream";

View file

@ -7,9 +7,9 @@
namespace dart {
// Translate from the legacy DebugEvent to a ServiceEvent.
static ServiceEvent::EventType TranslateEventType(
DebuggerEvent::EventType type) {
switch (type) {
static ServiceEvent::EventKind TranslateEventKind(
DebuggerEvent::EventType kind) {
switch (kind) {
case DebuggerEvent::kIsolateCreated:
return ServiceEvent::kIsolateStart;
@ -24,7 +24,6 @@ static ServiceEvent::EventType TranslateEventType(
case DebuggerEvent::kExceptionThrown:
return ServiceEvent::kPauseException;
default:
UNREACHABLE();
return ServiceEvent::kIllegal;
@ -33,12 +32,14 @@ static ServiceEvent::EventType TranslateEventType(
ServiceEvent::ServiceEvent(const DebuggerEvent* debugger_event)
: isolate_(debugger_event->isolate()),
type_(TranslateEventType(debugger_event->type())),
kind_(TranslateEventKind(debugger_event->type())),
breakpoint_(NULL),
top_frame_(NULL),
exception_(NULL),
inspectee_(NULL),
gc_stats_(NULL) {
gc_stats_(NULL),
bytes_(NULL),
bytes_length_(0) {
DebuggerEvent::EventType type = debugger_event->type();
if (type == DebuggerEvent::kBreakpointReached) {
set_breakpoint(debugger_event->breakpoint());
@ -54,8 +55,8 @@ ServiceEvent::ServiceEvent(const DebuggerEvent* debugger_event)
}
const char* ServiceEvent::EventTypeToCString(EventType type) {
switch (type) {
const char* ServiceEvent::KindAsCString() const {
switch (kind()) {
case kIsolateStart:
return "IsolateStart";
case kIsolateExit:
@ -84,6 +85,8 @@ const char* ServiceEvent::EventTypeToCString(EventType type) {
return "GC"; // TODO(koda): Change to GarbageCollected.
case kInspect:
return "Inspect";
case kEmbedder:
return embedder_kind();
case kDebuggerSettingsUpdate:
return "_DebuggerSettingsUpdate";
case kIllegal:
@ -96,11 +99,11 @@ const char* ServiceEvent::EventTypeToCString(EventType type) {
const char* ServiceEvent::stream_id() const {
switch (type()) {
switch (kind()) {
case kIsolateStart:
case kIsolateExit:
case kIsolateUpdate:
return "Isolate";
return Service::isolate_stream.id();
case kPauseStart:
case kPauseExit:
@ -113,10 +116,13 @@ const char* ServiceEvent::stream_id() const {
case kBreakpointRemoved:
case kInspect:
case kDebuggerSettingsUpdate:
return "Debug";
return Service::debug_stream.id();
case kGC:
return "GC";
return Service::gc_stream.id();
case kEmbedder:
return embedder_stream_id_;
default:
UNREACHABLE();
@ -128,9 +134,9 @@ const char* ServiceEvent::stream_id() const {
void ServiceEvent::PrintJSON(JSONStream* js) const {
JSONObject jsobj(js);
jsobj.AddProperty("type", "Event");
jsobj.AddProperty("kind", EventTypeToCString(type()));
jsobj.AddProperty("kind", KindAsCString());
jsobj.AddProperty("isolate", isolate());
if (type() == kPauseBreakpoint) {
if (kind() == kPauseBreakpoint) {
JSONArray jsarr(&jsobj, "pauseBreakpoints");
// TODO(rmacnak): If we are paused at more than one breakpoint,
// provide it here.
@ -142,7 +148,7 @@ void ServiceEvent::PrintJSON(JSONStream* js) const {
jsobj.AddProperty("breakpoint", breakpoint());
}
}
if (type() == kDebuggerSettingsUpdate) {
if (kind() == kDebuggerSettingsUpdate) {
JSONObject jssettings(&jsobj, "_debuggerSettings");
isolate()->debugger()->PrintSettingsToJSONObject(&jssettings);
}
@ -163,6 +169,9 @@ void ServiceEvent::PrintJSON(JSONStream* js) const {
isolate()->heap()->PrintToJSONObject(Heap::kNew, &jsobj);
isolate()->heap()->PrintToJSONObject(Heap::kOld, &jsobj);
}
if (bytes() != NULL) {
jsobj.AddPropertyBase64("bytes", bytes(), bytes_length());
}
}
} // namespace dart

View file

@ -13,7 +13,7 @@ namespace dart {
class ServiceEvent {
public:
enum EventType {
enum EventKind {
kIsolateStart, // New isolate has started
kIsolateExit, // Isolate has exited
kIsolateUpdate, // Isolate identity information has changed
@ -32,34 +32,52 @@ class ServiceEvent {
kGC,
kEmbedder,
kIllegal,
};
ServiceEvent(Isolate* isolate, EventType event_type)
ServiceEvent(Isolate* isolate, EventKind event_kind)
: isolate_(isolate),
type_(event_type),
kind_(event_kind),
embedder_kind_(NULL),
embedder_stream_id_(NULL),
breakpoint_(NULL),
top_frame_(NULL),
exception_(NULL),
inspectee_(NULL),
gc_stats_(NULL) {}
gc_stats_(NULL),
bytes_(NULL),
bytes_length_(0) {}
explicit ServiceEvent(const DebuggerEvent* debugger_event);
Isolate* isolate() const { return isolate_; }
EventType type() const { return type_; }
EventKind kind() const { return kind_; }
const char* embedder_kind() const { return embedder_kind_; }
const char* KindAsCString() const;
void set_embedder_kind(const char* embedder_kind) {
embedder_kind_ = embedder_kind;
}
const char* stream_id() const;
void set_embedder_stream_id(const char* stream_id) {
embedder_stream_id_ = stream_id;
}
Breakpoint* breakpoint() const {
return breakpoint_;
}
void set_breakpoint(Breakpoint* bpt) {
ASSERT(type() == kPauseBreakpoint ||
type() == kBreakpointAdded ||
type() == kBreakpointResolved ||
type() == kBreakpointRemoved);
ASSERT(kind() == kPauseBreakpoint ||
kind() == kBreakpointAdded ||
kind() == kBreakpointResolved ||
kind() == kBreakpointRemoved);
breakpoint_ = bpt;
}
@ -67,10 +85,10 @@ class ServiceEvent {
return top_frame_;
}
void set_top_frame(ActivationFrame* frame) {
ASSERT(type() == kPauseBreakpoint ||
type() == kPauseInterrupted ||
type() == kPauseException ||
type() == kResume);
ASSERT(kind() == kPauseBreakpoint ||
kind() == kPauseInterrupted ||
kind() == kPauseException ||
kind() == kResume);
top_frame_ = frame;
}
@ -78,7 +96,7 @@ class ServiceEvent {
return exception_;
}
void set_exception(const Object* exception) {
ASSERT(type_ == kPauseException);
ASSERT(kind_ == kPauseException);
exception_ = exception;
}
@ -86,7 +104,7 @@ class ServiceEvent {
return inspectee_;
}
void set_inspectee(const Object* inspectee) {
ASSERT(type_ == kInspect);
ASSERT(kind_ == kInspect);
inspectee_ = inspectee;
}
@ -98,18 +116,33 @@ class ServiceEvent {
gc_stats_ = gc_stats;
}
void PrintJSON(JSONStream* js) const;
const uint8_t* bytes() const {
return bytes_;
}
static const char* EventTypeToCString(EventType type);
intptr_t bytes_length() const {
return bytes_length_;
}
void set_bytes(const uint8_t* bytes, intptr_t bytes_length) {
bytes_ = bytes;
bytes_length_ = bytes_length;
}
void PrintJSON(JSONStream* js) const;
private:
Isolate* isolate_;
EventType type_;
EventKind kind_;
const char* embedder_kind_;
const char* embedder_stream_id_;
Breakpoint* breakpoint_;
ActivationFrame* top_frame_;
const Object* exception_;
const Object* inspectee_;
const Heap::GCStats* gc_stats_;
const uint8_t* bytes_;
intptr_t bytes_length_;
};
} // namespace dart

View file

@ -304,7 +304,8 @@ class ServiceIsolateNatives : public AllStatic {
Zone* zone = stack_zone.GetZone(); // Used by GET_NON_NULL_NATIVE_ARGUMENT.
HANDLESCOPE(isolate);
GET_NON_NULL_NATIVE_ARGUMENT(String, stream_id, arguments->NativeArgAt(0));
Service::ListenStream(stream_id.ToCString());
bool result = Service::ListenStream(stream_id.ToCString());
arguments->SetReturn(Bool::Get(result));
}
static void CancelStream(Dart_NativeArguments args) {