| // 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. | |
| #include "vm/service_event.h" | |
| #include "vm/debugger.h" | |
| #include "vm/message_handler.h" | |
| #include "vm/service_isolate.h" | |
| #include "vm/timeline.h" | |
| namespace dart { | |
| #ifndef PRODUCT | |
| ServiceEvent::ServiceEvent(EventKind event_kind) | |
| : ServiceEvent(nullptr, nullptr, event_kind) {} | |
| ServiceEvent::ServiceEvent(IsolateGroup* isolate_group, EventKind event_kind) | |
| : ServiceEvent(isolate_group, nullptr, event_kind) {} | |
| ServiceEvent::ServiceEvent(Isolate* isolate, EventKind event_kind) | |
| : ServiceEvent(isolate != nullptr ? isolate->group() : nullptr, | |
| isolate, | |
| event_kind) {} | |
| ServiceEvent::ServiceEvent(IsolateGroup* isolate_group, | |
| Isolate* isolate, | |
| EventKind event_kind) | |
| : isolate_(isolate), | |
| isolate_group_(isolate_group), | |
| kind_(event_kind), | |
| flag_name_(nullptr), | |
| flag_new_value_(nullptr), | |
| previous_tag_(nullptr), | |
| updated_tag_(nullptr), | |
| embedder_kind_(nullptr), | |
| embedder_stream_id_(nullptr), | |
| breakpoint_(nullptr), | |
| top_frame_(nullptr), | |
| timeline_event_block_(nullptr), | |
| extension_rpc_(nullptr), | |
| exception_(nullptr), | |
| reload_error_(nullptr), | |
| spawn_token_(nullptr), | |
| spawn_error_(nullptr), | |
| at_async_jump_(false), | |
| inspectee_(nullptr), | |
| gc_stats_(nullptr), | |
| bytes_(nullptr), | |
| bytes_length_(0), | |
| milliseconds_overdue_(0), | |
| timestamp_(OS::GetCurrentTimeMillis()) { | |
| // VM-internal isolates should never post service events outside of the | |
| // following cases: | |
| // - The `Isolate` service object uses a service event to represent the | |
| // current running state of the isolate, so we need to allow for system | |
| // isolates to create `kResume` and `kNone` events for this purpose. The | |
| // `kResume` event represents a running isolate and the `kNone` event is | |
| // returned for an isolate that has not yet been marked as runnable (see | |
| // "pauseEvent" in Isolate::PrintJSON). | |
| // - `kEmbedder` events relaying output printed on stdout/stderr may be | |
| // posted from any isolate. | |
| // - `kCpuSamples` events may be posted from any isolate. | |
| // - `kTimerSignificantlyOverdue` events may be posted from any isolate. | |
| ASSERT(isolate == nullptr || !Isolate::IsVMInternalIsolate(isolate) || | |
| (Isolate::IsVMInternalIsolate(isolate) && | |
| (event_kind == ServiceEvent::kResume || | |
| event_kind == ServiceEvent::kNone || | |
| event_kind == ServiceEvent::kEmbedder || | |
| event_kind == ServiceEvent::kCpuSamples || | |
| event_kind == ServiceEvent::kTimerSignificantlyOverdue))); | |
| if ((event_kind == ServiceEvent::kPauseStart) || | |
| (event_kind == ServiceEvent::kPauseExit)) { | |
| timestamp_ = isolate_->message_handler()->paused_timestamp(); | |
| } else if (event_kind == ServiceEvent::kResume) { | |
| timestamp_ = isolate_->last_resume_timestamp(); | |
| } | |
| ASSERT(timestamp_ > -1); | |
| } | |
| void ServiceEvent::UpdateTimestamp() { | |
| timestamp_ = OS::GetCurrentTimeMillis(); | |
| } | |
| const char* ServiceEvent::KindAsCString() const { | |
| switch (kind()) { | |
| case kVMUpdate: | |
| return "VMUpdate"; | |
| case kVMFlagUpdate: | |
| return "VMFlagUpdate"; | |
| case kIsolateStart: | |
| return "IsolateStart"; | |
| case kIsolateRunnable: | |
| return "IsolateRunnable"; | |
| case kIsolateExit: | |
| return "IsolateExit"; | |
| case kIsolateUpdate: | |
| return "IsolateUpdate"; | |
| case kServiceExtensionAdded: | |
| return "ServiceExtensionAdded"; | |
| case kIsolateReload: | |
| return "IsolateReload"; | |
| case kPauseStart: | |
| return "PauseStart"; | |
| case kPauseExit: | |
| return "PauseExit"; | |
| case kPauseBreakpoint: | |
| return "PauseBreakpoint"; | |
| case kPauseInterrupted: | |
| return "PauseInterrupted"; | |
| case kPauseException: | |
| return "PauseException"; | |
| case kPausePostRequest: | |
| return "PausePostRequest"; | |
| case kNone: | |
| return "None"; | |
| case kResume: | |
| return "Resume"; | |
| case kBreakpointAdded: | |
| return "BreakpointAdded"; | |
| case kBreakpointResolved: | |
| return "BreakpointResolved"; | |
| case kBreakpointRemoved: | |
| return "BreakpointRemoved"; | |
| case kBreakpointUpdated: | |
| return "BreakpointUpdated"; | |
| case kGC: | |
| return "GC"; // TODO(koda): Change to GarbageCollected. | |
| case kInspect: | |
| return "Inspect"; | |
| case kEmbedder: | |
| return embedder_kind(); | |
| case kLogging: | |
| return "Logging"; | |
| case kTimerSignificantlyOverdue: | |
| return "TimerSignificantlyOverdue"; | |
| case kDebuggerSettingsUpdate: | |
| return "_DebuggerSettingsUpdate"; | |
| case kIllegal: | |
| return "Illegal"; | |
| case kExtension: | |
| return "Extension"; | |
| case kTimelineEvents: | |
| return "TimelineEvents"; | |
| case kTimelineStreamSubscriptionsUpdate: | |
| return "TimelineStreamSubscriptionsUpdate"; | |
| case kUserTagChanged: | |
| return "UserTagChanged"; | |
| case kCpuSamples: | |
| return "CpuSamples"; | |
| default: | |
| UNREACHABLE(); | |
| return "Unknown"; | |
| } | |
| } | |
| const StreamInfo* ServiceEvent::stream_info() const { | |
| switch (kind()) { | |
| case kVMUpdate: | |
| case kVMFlagUpdate: | |
| return &Service::vm_stream; | |
| case kIsolateStart: | |
| case kIsolateRunnable: | |
| case kIsolateExit: | |
| case kIsolateUpdate: | |
| case kIsolateReload: | |
| case kServiceExtensionAdded: | |
| return &Service::isolate_stream; | |
| case kPauseStart: | |
| case kPauseExit: | |
| case kPauseBreakpoint: | |
| case kPauseInterrupted: | |
| case kPauseException: | |
| case kPausePostRequest: | |
| case kNone: | |
| case kResume: | |
| case kBreakpointAdded: | |
| case kBreakpointResolved: | |
| case kBreakpointRemoved: | |
| case kBreakpointUpdated: | |
| case kInspect: | |
| case kDebuggerSettingsUpdate: | |
| return &Service::debug_stream; | |
| case kGC: | |
| return &Service::gc_stream; | |
| case kLogging: | |
| return &Service::logging_stream; | |
| case kTimerSignificantlyOverdue: | |
| return &Service::timer_stream; | |
| case kExtension: | |
| return &Service::extension_stream; | |
| case kTimelineEvents: | |
| case kTimelineStreamSubscriptionsUpdate: | |
| return &Service::timeline_stream; | |
| case kEmbedder: | |
| return nullptr; | |
| case kCpuSamples: | |
| case kUserTagChanged: | |
| return &Service::profiler_stream; | |
| default: | |
| UNREACHABLE(); | |
| return nullptr; | |
| } | |
| } | |
| const char* ServiceEvent::stream_id() const { | |
| const StreamInfo* stream = stream_info(); | |
| if (stream == nullptr) { | |
| ASSERT(kind() == kEmbedder); | |
| return embedder_stream_id_; | |
| } else { | |
| return stream->id(); | |
| } | |
| } | |
| void ServiceEvent::PrintJSON(JSONStream* js) const { | |
| JSONObject jsobj(js); | |
| PrintJSONHeader(&jsobj); | |
| if (kind() == kVMFlagUpdate) { | |
| jsobj.AddProperty("flag", flag_name()); | |
| // For backwards compatibility, "new_value" is also provided. | |
| jsobj.AddProperty("newValue", flag_new_value()); | |
| } | |
| if (kind() == kUserTagChanged) { | |
| jsobj.AddProperty("previousTag", previous_tag()); | |
| jsobj.AddProperty("updatedTag", updated_tag()); | |
| } | |
| if (kind() == kIsolateReload) { | |
| if (reload_error_ == nullptr) { | |
| jsobj.AddProperty("status", "success"); | |
| } else { | |
| jsobj.AddProperty("status", "failure"); | |
| jsobj.AddProperty("reloadFailureReason", | |
| reload_error()->ToErrorCString()); | |
| } | |
| } | |
| if (kind() == kServiceExtensionAdded) { | |
| ASSERT(extension_rpc_ != nullptr); | |
| jsobj.AddProperty("extensionRPC", extension_rpc_->ToCString()); | |
| } | |
| if (kind() == kPauseBreakpoint) { | |
| JSONArray jsarr(&jsobj, "pauseBreakpoints"); | |
| // TODO(rmacnak): If we are paused at more than one breakpoint, | |
| // provide it here. | |
| if (breakpoint() != nullptr) { | |
| jsarr.AddValue(breakpoint()); | |
| } | |
| } else { | |
| if (breakpoint() != nullptr) { | |
| jsobj.AddProperty("breakpoint", breakpoint()); | |
| } | |
| } | |
| if (kind() == kTimelineEvents) { | |
| jsobj.AddProperty("timelineEvents", timeline_event_block_); | |
| } | |
| if (kind() == kTimelineStreamSubscriptionsUpdate) { | |
| JSONArray arr(&jsobj, "updatedStreams"); | |
| Timeline::PrintFlagsToJSONArray(&arr); | |
| } | |
| if (kind() == kDebuggerSettingsUpdate) { | |
| JSONObject jssettings(&jsobj, "_debuggerSettings"); | |
| isolate()->debugger()->PrintSettingsToJSONObject(&jssettings); | |
| } | |
| #if !defined(DART_PRECOMPILED_RUNTIME) | |
| if (top_frame() != nullptr) { | |
| JSONObject jsFrame(&jsobj, "topFrame"); | |
| top_frame()->PrintToJSONObject(&jsFrame); | |
| intptr_t index = 0; // Avoid ambiguity in call to AddProperty. | |
| jsFrame.AddProperty("index", index); | |
| } | |
| #endif | |
| if (exception() != nullptr) { | |
| jsobj.AddProperty("exception", *(exception())); | |
| } | |
| if (at_async_jump()) { | |
| jsobj.AddProperty("atAsyncSuspension", true); | |
| } | |
| if (inspectee() != nullptr) { | |
| jsobj.AddProperty("inspectee", *(inspectee())); | |
| } | |
| if (gc_stats() != nullptr) { | |
| jsobj.AddProperty("reason", Heap::GCReasonToString(gc_stats()->reason_)); | |
| jsobj.AddProperty("gcType", Heap::GCTypeToString(gc_stats()->type_)); | |
| isolate_group()->heap()->PrintToJSONObject(Heap::kNew, &jsobj); | |
| isolate_group()->heap()->PrintToJSONObject(Heap::kOld, &jsobj); | |
| } | |
| if (bytes() != nullptr) { | |
| jsobj.AddPropertyBase64("bytes", bytes(), bytes_length()); | |
| } | |
| if (kind() == kLogging) { | |
| JSONObject logRecord(&jsobj, "logRecord"); | |
| logRecord.AddProperty("type", "LogRecord"); | |
| logRecord.AddProperty64("sequenceNumber", log_record_.sequence_number); | |
| logRecord.AddPropertyTimeMillis("time", log_record_.timestamp); | |
| logRecord.AddProperty64("level", log_record_.level); | |
| logRecord.AddProperty("loggerName", *(log_record_.name)); | |
| logRecord.AddProperty("message", *(log_record_.message)); | |
| logRecord.AddProperty("zone", *(log_record_.zone)); | |
| logRecord.AddProperty("error", *(log_record_.error)); | |
| logRecord.AddProperty("stackTrace", *(log_record_.stack_trace)); | |
| } | |
| if (kind() == kTimerSignificantlyOverdue) { | |
| jsobj.AddPropertyF( | |
| "details", | |
| "A timer should have fired %" Pd | |
| " ms ago, but just fired now. This means that timers were blocked from " | |
| "firing for nearly %" Pd | |
| " ms. Some possible causes of this are: asynchronous operations were " | |
| "blocked by uninterruptible synchronous ones, or your program was " | |
| "frozen by the OS to conserve resources.", | |
| milliseconds_overdue(), milliseconds_overdue()); | |
| } | |
| if (kind() == kExtension) { | |
| js->AppendSerializedObject("extensionData", | |
| extension_event_.event_data->ToCString()); | |
| } | |
| if (kind() == kCpuSamples) { | |
| JSONObject cpu_profile(&jsobj, "cpuSamples"); | |
| cpu_profile_->PrintProfileJSON(&cpu_profile, /*include_code_samples=*/false, | |
| /*is_event=*/true); | |
| } | |
| } | |
| void ServiceEvent::PrintJSONHeader(JSONObject* jsobj) const { | |
| ASSERT(jsobj != nullptr); | |
| jsobj->AddProperty("type", "Event"); | |
| jsobj->AddProperty("kind", KindAsCString()); | |
| if (kind() == kExtension) { | |
| ASSERT(extension_event_.event_kind != nullptr); | |
| jsobj->AddProperty("extensionKind", | |
| extension_event_.event_kind->ToCString()); | |
| } | |
| if (isolate_group() != nullptr) { | |
| jsobj->AddProperty("isolateGroup", isolate_group()); | |
| } | |
| if (isolate() != nullptr) { | |
| ASSERT(isolate_group() != nullptr); | |
| jsobj->AddProperty("isolate", isolate()); | |
| } | |
| if (isolate_group() == nullptr && isolate() == nullptr) { | |
| jsobj->AddPropertyVM("vm"); | |
| } | |
| ASSERT(timestamp_ != -1); | |
| jsobj->AddPropertyTimeMillis("timestamp", timestamp_); | |
| } | |
| #endif // !PRODUCT | |
| } // namespace dart |