From eb563faf6dd8f2f4e252c4ced3d790a4945ea689 Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Mon, 12 May 2025 10:46:39 -0700 Subject: [PATCH] [vm, gc] Report RSS as timeline counter events, sampled before and after GC. Catapult will display a nice graph for this. TEST=view timeline Change-Id: I6b6fdede463b37dbb10841cfa7c1e08c95e7cb62 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427060 Commit-Queue: Ryan Macnak Reviewed-by: Slava Egorov --- .../test/get_vm_timeline_rpc_test.dart | 4 ++++ .../tests/service/get_vm_timeline_rpc_test.dart | 4 ++++ runtime/vm/heap/heap.cc | 15 +++++++++++++++ runtime/vm/os.h | 4 ++++ runtime/vm/os_android.cc | 17 +++++++++++++++++ runtime/vm/os_fuchsia.cc | 12 ++++++++++++ runtime/vm/os_linux.cc | 17 +++++++++++++++++ runtime/vm/os_macos.cc | 12 ++++++++++++ runtime/vm/os_win.cc | 16 ++++++++++++++++ runtime/vm/timeline.cc | 4 ++++ runtime/vm/timeline.h | 1 + 11 files changed, 106 insertions(+) diff --git a/pkg/vm_service/test/get_vm_timeline_rpc_test.dart b/pkg/vm_service/test/get_vm_timeline_rpc_test.dart index 656d02314eb..fc793ab9a18 100644 --- a/pkg/vm_service/test/get_vm_timeline_rpc_test.dart +++ b/pkg/vm_service/test/get_vm_timeline_rpc_test.dart @@ -111,6 +111,10 @@ void allEventsHaveIsolateNumber(List events) { // Skip API category events which sometimes don't have an isolate. continue; } + if (event['name'] == 'RSS' && event['ph'] == 'C') { + // Skip RSS events, which don't have an isolate or isolate group. + continue; + } if (event['cat'] == 'Embedder' && (event['name'] == 'DFE::ReadScript' || event['name'] == 'CreateIsolateGroupAndSetupHelper' || diff --git a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart index 6067d75fcdb..dc81658f4d5 100644 --- a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart +++ b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart @@ -103,6 +103,10 @@ void allEventsHaveIsolateNumber(List events) { // Skip API category events which sometimes don't have an isolate. continue; } + if (event['name'] == 'RSS' && event['ph'] == 'C') { + // Skip RSS events, which don't have an isolate or isolate group. + continue; + } if (event['cat'] == 'Embedder' && (event['name'] == 'DFE::ReadScript' || event['name'] == 'CreateIsolateGroupAndSetupHelper')) { diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc index e4a9732c9e2..01e1167d168 100644 --- a/runtime/vm/heap/heap.cc +++ b/runtime/vm/heap/heap.cc @@ -1002,6 +1002,19 @@ void Heap::PrintMemoryUsageJSON(JSONObject* jsobj) const { } #endif // PRODUCT +static void RecordRSS() { +#if defined(SUPPORT_TIMELINE) + TimelineEvent* event = Timeline::GetGCStream()->StartEvent(); + if (event != nullptr) { + event->Counter("RSS"); + event->SetNumArguments(1); + event->FormatArgument(0, "value", "%" Pd, OS::CurrentRSS()); + event->ClearIsolateGroupId(); // This value is per-process. + event->Complete(); + } +#endif +} + void Heap::RecordBeforeGC(GCType type, GCReason reason) { stats_.num_++; stats_.type_ = type; @@ -1010,6 +1023,7 @@ void Heap::RecordBeforeGC(GCType type, GCReason reason) { stats_.before_.new_ = new_space_.GetCurrentUsage(); stats_.before_.old_ = old_space_.GetCurrentUsage(); stats_.before_.store_buffer_ = isolate_group_->store_buffer()->Size(); + RecordRSS(); } void Heap::RecordAfterGC(GCType type) { @@ -1025,6 +1039,7 @@ void Heap::RecordAfterGC(GCType type) { stats_.after_.new_ = new_space_.GetCurrentUsage(); stats_.after_.old_ = old_space_.GetCurrentUsage(); stats_.after_.store_buffer_ = isolate_group_->store_buffer()->Size(); + RecordRSS(); #ifndef PRODUCT // For now we'll emit the same GC events on all isolates. if (Service::gc_stream.enabled()) { diff --git a/runtime/vm/os.h b/runtime/vm/os.h index 9ec56357c9f..29875c4a9be 100644 --- a/runtime/vm/os.h +++ b/runtime/vm/os.h @@ -68,6 +68,10 @@ class OS { // Returns number of available processor cores. static int NumberOfAvailableProcessors(); + // Returns the current resident set size in bytes, or 0 if it could not be + // determined. + static uintptr_t CurrentRSS(); + // Sleep the currently executing thread for millis ms. static void Sleep(int64_t millis); diff --git a/runtime/vm/os_android.cc b/runtime/vm/os_android.cc index b60658c41b5..64a5d53a6e3 100644 --- a/runtime/vm/os_android.cc +++ b/runtime/vm/os_android.cc @@ -204,6 +204,23 @@ int OS::NumberOfAvailableProcessors() { return sysconf(_SC_NPROCESSORS_ONLN); } +uintptr_t OS::CurrentRSS() { + // The second value in /proc/self/statm is the current RSS in pages. + // It is not possible to use getrusage() because the interested fields are not + // implemented by the linux kernel. + FILE* statm = fopen("/proc/self/statm", "r"); + if (statm == nullptr) { + return 0; + } + int64_t current_rss_pages = 0; + int matches = fscanf(statm, "%*s%" Pd64 "", ¤t_rss_pages); + fclose(statm); + if (matches != 1) { + return 0; + } + return current_rss_pages * getpagesize(); +} + void OS::Sleep(int64_t millis) { int64_t micros = millis * kMicrosecondsPerMillisecond; SleepMicros(micros); diff --git a/runtime/vm/os_fuchsia.cc b/runtime/vm/os_fuchsia.cc index 35eca99e6ab..eb3860e8707 100644 --- a/runtime/vm/os_fuchsia.cc +++ b/runtime/vm/os_fuchsia.cc @@ -488,6 +488,18 @@ int OS::NumberOfAvailableProcessors() { return sysconf(_SC_NPROCESSORS_CONF); } +uintptr_t OS::CurrentRSS() { + zx_info_task_stats_t task_stats; + zx_handle_t process = zx_process_self(); + zx_status_t status = + zx_object_get_info(process, ZX_INFO_TASK_STATS, &task_stats, + sizeof(task_stats), nullptr, nullptr); + if (status != ZX_OK) { + return 0; + } + return task_stats.mem_private_bytes + task_stats.mem_shared_bytes; +} + void OS::Sleep(int64_t millis) { SleepMicros(millis * kMicrosecondsPerMillisecond); } diff --git a/runtime/vm/os_linux.cc b/runtime/vm/os_linux.cc index 885d8934afb..f8c7bc519d9 100644 --- a/runtime/vm/os_linux.cc +++ b/runtime/vm/os_linux.cc @@ -521,6 +521,23 @@ int OS::NumberOfAvailableProcessors() { return sysconf(_SC_NPROCESSORS_ONLN); } +uintptr_t OS::CurrentRSS() { + // The second value in /proc/self/statm is the current RSS in pages. + // It is not possible to use getrusage() because the interested fields are not + // implemented by the linux kernel. + FILE* statm = fopen("/proc/self/statm", "r"); + if (statm == nullptr) { + return 0; + } + int64_t current_rss_pages = 0; + int matches = fscanf(statm, "%*s%" Pd64 "", ¤t_rss_pages); + fclose(statm); + if (matches != 1) { + return 0; + } + return current_rss_pages * getpagesize(); +} + void OS::Sleep(int64_t millis) { int64_t micros = millis * kMicrosecondsPerMillisecond; SleepMicros(micros); diff --git a/runtime/vm/os_macos.cc b/runtime/vm/os_macos.cc index 33eb5bd67c1..26ba160f3f6 100644 --- a/runtime/vm/os_macos.cc +++ b/runtime/vm/os_macos.cc @@ -123,6 +123,18 @@ int OS::NumberOfAvailableProcessors() { return sysconf(_SC_NPROCESSORS_ONLN); } +uintptr_t OS::CurrentRSS() { + struct mach_task_basic_info info; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + kern_return_t result = + task_info(mach_task_self(), MACH_TASK_BASIC_INFO, + reinterpret_cast(&info), &infoCount); + if (result != KERN_SUCCESS) { + return 0; + } + return info.resident_size; +} + void OS::Sleep(int64_t millis) { int64_t micros = millis * kMicrosecondsPerMillisecond; SleepMicros(micros); diff --git a/runtime/vm/os_win.cc b/runtime/vm/os_win.cc index 570102a834f..12d056f7d07 100644 --- a/runtime/vm/os_win.cc +++ b/runtime/vm/os_win.cc @@ -238,6 +238,22 @@ int OS::NumberOfAvailableProcessors() { return info.dwNumberOfProcessors; } +uintptr_t OS::CurrentRSS() { +// Although the documentation at +// https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo +// claims that GetProcessMemoryInfo is UWP compatible, it is actually not +// hence this function cannot work when compiled in UWP mode. +#ifdef DART_TARGET_OS_WINDOWS_UWP + return 0; +#else + PROCESS_MEMORY_COUNTERS pmc; + if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + return 0; + } + return pmc.WorkingSetSize; +#endif +} + void OS::Sleep(int64_t millis) { ::Sleep(millis); } diff --git a/runtime/vm/timeline.cc b/runtime/vm/timeline.cc index 481222456ec..8e010f3d6bb 100644 --- a/runtime/vm/timeline.cc +++ b/runtime/vm/timeline.cc @@ -1220,6 +1220,10 @@ bool TimelineEvent::HasIsolateGroupId() const { return isolate_group_id_ != ILLEGAL_ISOLATE_GROUP_ID; } +void TimelineEvent::ClearIsolateGroupId() { + isolate_group_id_ = ILLEGAL_ISOLATE_GROUP_ID; +} + TimelineTrackMetadata::TimelineTrackMetadata(intptr_t pid, intptr_t tid, CStringUniquePtr&& track_name) diff --git a/runtime/vm/timeline.h b/runtime/vm/timeline.h index 9c03f499c0c..f22a1890877 100644 --- a/runtime/vm/timeline.h +++ b/runtime/vm/timeline.h @@ -457,6 +457,7 @@ class TimelineEvent { bool HasIsolateId() const; bool HasIsolateGroupId() const; + void ClearIsolateGroupId(); // The lowest time value stored in this event. int64_t LowTime() const;