| // Copyright (c) 2025, 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. |
| |
| #if !defined(PRODUCT) |
| |
| #include "vm/microtask_mirror_queues.h" |
| |
| #include <utility> |
| |
| #include "platform/hashmap.h" |
| #include "vm/flags.h" |
| #include "vm/object.h" |
| #include "vm/timeline.h" |
| |
| namespace dart { |
| |
| DEFINE_FLAG( |
| bool, |
| profile_microtasks, |
| false, |
| "Record information about each microtask. Information about completed " |
| "microtasks will be written to the \"Microtask\" timeline stream."); |
| |
| MicrotaskMirrorQueue* MicrotaskMirrorQueues::GetQueue(int64_t isolate_id) { |
| void* key = reinterpret_cast<void*>(isolate_id); |
| const intptr_t hash = Utils::WordHash(isolate_id); |
| |
| MutexLocker ml(&isolate_id_to_queue_lock_); |
| |
| SimpleHashMap::Entry* entry = isolate_id_to_queue_.Lookup(key, hash, true); |
| if (entry->value == nullptr) { |
| entry->value = new MicrotaskMirrorQueue(); |
| } |
| return static_cast<MicrotaskMirrorQueue*>(entry->value); |
| } |
| |
| void MicrotaskMirrorQueue::OnScheduleAsyncCallback(const StackTrace& st) { |
| if (is_disabled_) { |
| return; |
| } |
| |
| queue_.PushBack(MicrotaskMirrorQueueEntry( |
| next_available_id_++, CStringUniquePtr(Utils::StrDup(st.ToCString())))); |
| } |
| |
| void MicrotaskMirrorQueue::OnSchedulePriorityAsyncCallback() { |
| // If this function is called, it means that the microtask queue can no longer |
| // be accurately modeled by a |ListQueue|. This only ever gets called when an |
| // exception goes unhandled, so we just handle the situation by disabling all |
| // further reads from / writes to this queue. |
| is_disabled_ = true; |
| } |
| |
| void MicrotaskMirrorQueue::OnAsyncCallbackComplete(int64_t start_time, |
| int64_t end_time) { |
| if (is_disabled_) { |
| return; |
| } |
| |
| ASSERT(queue_.Length() >= 1); |
| MicrotaskMirrorQueueEntry&& front = std::move(queue_.PopFront()); |
| |
| TimelineStream* stream = Timeline::GetMicrotaskStream(); |
| ASSERT(stream != nullptr); |
| TimelineEvent* event = stream->StartEvent(); |
| if (event != nullptr) { |
| if (start_time < end_time) { |
| event->Duration("Microtask", start_time, end_time); |
| } else { |
| event->Instant("Microtask", start_time); |
| } |
| |
| event->SetNumArguments(2); |
| event->FormatArgument(0, "microtaskId", "%" Pd, front.id()); |
| event->SetArgument(1, "stack trace captured when microtask was enqueued", |
| front.ReleaseStackTrace()); |
| event->Complete(); |
| } |
| } |
| |
| void MicrotaskMirrorQueue::PrintJSON(JSONStream& js) const { |
| ASSERT(!is_disabled_); |
| |
| JSONObject jsobj_topLevel(&js); |
| jsobj_topLevel.AddProperty("type", "QueuedMicrotasks"); |
| jsobj_topLevel.AddProperty64("timestamp", OS::GetCurrentTimeMicros()); |
| |
| JSONArray jsarr(&jsobj_topLevel, "microtasks"); |
| queue_.ForEach([&jsarr](const MicrotaskMirrorQueueEntry& entry) { |
| JSONObject jsobj_entry(&jsarr); |
| jsobj_entry.AddProperty("type", "Microtask"); |
| jsobj_entry.AddProperty("id", entry.id()); |
| jsobj_entry.AddProperty("stackTrace", entry.stack_trace().get()); |
| }); |
| } |
| |
| Mutex MicrotaskMirrorQueues::isolate_id_to_queue_lock_; |
| |
| SimpleHashMap MicrotaskMirrorQueues::isolate_id_to_queue_( |
| &SimpleHashMap::SamePointerValue, |
| MicrotaskMirrorQueues::kIsolateIdToQueueInitialCapacity); |
| |
| void MicrotaskMirrorQueues::CleanUp() { |
| for (SimpleHashMap::Entry* entry = isolate_id_to_queue_.Start(); |
| entry != nullptr; entry = isolate_id_to_queue_.Next(entry)) { |
| MicrotaskMirrorQueue* value = |
| static_cast<MicrotaskMirrorQueue*>(entry->value); |
| delete value; |
| } |
| } |
| |
| } // namespace dart |
| |
| #endif // !defined(PRODUCT) |