Flutter Engine
 
Loading...
Searching...
No Matches
profiler_metrics_ios.mm
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#import <Foundation/Foundation.h>
8
12
14
15namespace {
16
17// RAII holder for `thread_array_t` this is so any early returns in
18// `ProfilerMetricsIOS::CpuUsage` don't leak them.
20 public:
21 thread_array_t threads = NULL;
22 mach_msg_type_number_t thread_count = 0;
23
24 MachThreads() = default;
25
27 [[maybe_unused]] kern_return_t kernel_return_code = vm_deallocate(
28 mach_task_self(), reinterpret_cast<vm_offset_t>(threads), thread_count * sizeof(thread_t));
29 FML_DCHECK(kernel_return_code == KERN_SUCCESS) << "Failed to deallocate thread infos.";
30 }
31
32 private:
34};
35
36} // namespace
37
38#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
39 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
40
41namespace fml {
42
43/// fml::CFRef retain and release implementations for io_object_t and related types.
44template <>
45struct CFRefTraits<io_object_t> {
46 static constexpr io_object_t kNullValue = 0;
47 static void Retain(io_object_t instance) { IOObjectRetain(instance); }
48 static void Release(io_object_t instance) { IOObjectRelease(instance); }
49};
50
51} // namespace fml
52
53#endif
54
55namespace flutter {
56namespace {
57
58#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
59 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
60
61std::optional<GpuUsageInfo> FindGpuUsageInfo(io_iterator_t iterator) {
62 for (fml::CFRef<io_registry_entry_t> reg_entry(IOIteratorNext(iterator)); reg_entry.Get();
63 reg_entry.Reset(IOIteratorNext(iterator))) {
64 CFMutableDictionaryRef cf_service_dictionary;
65 if (IORegistryEntryCreateCFProperties(reg_entry.Get(), &cf_service_dictionary,
66 kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
67 continue;
68 }
69 // Transfer ownership to ARC-managed pointer.
70 NSDictionary* service_dictionary = (__bridge_transfer NSDictionary*)cf_service_dictionary;
71 cf_service_dictionary = nullptr;
72 NSDictionary* performanceStatistics = service_dictionary[@"PerformanceStatistics"];
73 NSNumber* utilization = performanceStatistics[@"Device Utilization %"];
74 if (utilization) {
75 return (GpuUsageInfo){.percent_usage = [utilization doubleValue]};
76 }
77 }
78 return std::nullopt;
79}
80
81[[maybe_unused]] std::optional<GpuUsageInfo> FindSimulatorGpuUsageInfo() {
82 io_iterator_t io_iterator;
84 &io_iterator) == kIOReturnSuccess) {
85 fml::CFRef<io_iterator_t> iterator(io_iterator);
86 return FindGpuUsageInfo(iterator.Get());
87 }
88 return std::nullopt;
89}
90
91[[maybe_unused]] std::optional<GpuUsageInfo> FindDeviceGpuUsageInfo() {
92 io_iterator_t io_iterator;
94 &io_iterator) == kIOReturnSuccess) {
95 fml::CFRef<io_iterator_t> iterator(io_iterator);
96 for (fml::CFRef<io_registry_entry_t> reg_entry(IOIteratorNext(iterator.Get())); reg_entry.Get();
97 reg_entry.Reset(IOIteratorNext(iterator.Get()))) {
98 io_iterator_t io_inner_iterator;
99 if (IORegistryEntryGetChildIterator(reg_entry.Get(), kIOServicePlane, &io_inner_iterator) ==
101 fml::CFRef<io_iterator_t> inner_iterator(io_inner_iterator);
102 std::optional<GpuUsageInfo> result = FindGpuUsageInfo(inner_iterator.Get());
103 if (result.has_value()) {
104 return result;
105 }
106 }
107 }
108 }
109 return std::nullopt;
110}
111
112#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG ||
113 // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
114
115std::optional<GpuUsageInfo> PollGpuUsage() {
116#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE || \
117 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_JIT_RELEASE)
118 return std::nullopt;
119#elif TARGET_IPHONE_SIMULATOR
120 return FindSimulatorGpuUsageInfo();
121#elif TARGET_OS_IOS
122 return FindDeviceGpuUsageInfo();
123#endif // TARGET_IPHONE_SIMULATOR
124}
125} // namespace
126
128 return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage(), .gpu_usage = PollGpuUsage()};
129}
130
131std::optional<CpuUsageInfo> ProfilerMetricsIOS::CpuUsage() {
132 kern_return_t kernel_return_code;
133 MachThreads mach_threads = MachThreads();
134
135 // Get threads in the task
136 kernel_return_code =
137 task_threads(mach_task_self(), &mach_threads.threads, &mach_threads.thread_count);
138 if (kernel_return_code != KERN_SUCCESS) {
139 return std::nullopt;
140 }
141
142 double total_cpu_usage = 0.0;
143 uint32_t num_threads = mach_threads.thread_count;
144
145 // Add the CPU usage for each thread. It should be noted that there may be some CPU usage missing
146 // from this calculation. If a thread ends between calls to this routine, then its info will be
147 // lost. We could solve this by installing a callback using pthread_key_create. The callback would
148 // report the thread is ending and allow the code to get the CPU usage. But we need to call
149 // pthread_setspecific in each thread to set the key's value to a non-null value for the callback
150 // to work. If we really need this information and if we have a good mechanism for calling
151 // pthread_setspecific in every thread, then we can include that value in the CPU usage.
152 for (mach_msg_type_number_t i = 0; i < mach_threads.thread_count; i++) {
153 thread_basic_info_data_t basic_thread_info;
154 mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
155 kernel_return_code =
156 thread_info(mach_threads.threads[i], THREAD_BASIC_INFO,
157 reinterpret_cast<thread_info_t>(&basic_thread_info), &thread_info_count);
158 switch (kernel_return_code) {
159 case KERN_SUCCESS: {
160 const double current_thread_cpu_usage =
161 basic_thread_info.cpu_usage / static_cast<float>(TH_USAGE_SCALE);
162 total_cpu_usage += current_thread_cpu_usage;
163 break;
164 }
165 case MACH_SEND_TIMEOUT:
166 case MACH_SEND_TIMED_OUT:
167 case MACH_SEND_INVALID_DEST:
168 // Ignore as this thread been destroyed. The possible return codes are not really well
169 // documented. This handling is inspired from the following sources:
170 // - https://opensource.apple.com/source/xnu/xnu-4903.221.2/tests/task_inspect.c.auto.html
171 // - https://github.com/apple/swift-corelibs-libdispatch/blob/main/src/queue.c#L6617
172 num_threads--;
173 break;
174 default:
175 return std::nullopt;
176 }
177 }
178
179 flutter::CpuUsageInfo cpu_usage_info = {.num_threads = num_threads,
180 .total_cpu_usage = total_cpu_usage * 100.0};
181 return cpu_usage_info;
182}
183
184std::optional<MemoryUsageInfo> ProfilerMetricsIOS::MemoryUsage() {
185 kern_return_t kernel_return_code;
186 task_vm_info_data_t task_memory_info;
187 mach_msg_type_number_t task_memory_info_count = TASK_VM_INFO_COUNT;
188
189 kernel_return_code =
190 task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&task_memory_info),
191 &task_memory_info_count);
192 if (kernel_return_code != KERN_SUCCESS) {
193 return std::nullopt;
194 }
195
196 // `phys_footprint` is Apple's recommended way to measure app's memory usage. It provides the
197 // best approximate to xcode memory gauge. According to its source code explanation, the physical
198 // footprint mainly consists of app's internal memory data and IOKit mappings. `resident_size`
199 // is the total physical memory used by the app, so we simply do `resident_size - phys_footprint`
200 // to obtain the shared memory usage.
201 const double dirty_memory_usage =
202 static_cast<double>(task_memory_info.phys_footprint) / 1024.0 / 1024.0;
203 const double owned_shared_memory_usage =
204 static_cast<double>(task_memory_info.resident_size) / 1024.0 / 1024.0 - dirty_memory_usage;
205 flutter::MemoryUsageInfo memory_usage_info = {
206 .dirty_memory_usage = dirty_memory_usage,
207 .owned_shared_memory_usage = owned_shared_memory_usage};
208 return memory_usage_info;
209}
210
211} // namespace flutter
kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it)
io_object_t io_iterator_t
Definition IOKit.h:33
const mach_port_t kIOMasterPortDefault
constexpr const char * kIOServicePlane
Definition IOKit.h:28
CFMutableDictionaryRef IOServiceNameMatching(const char *name) CF_RETURNS_RETAINED
kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef *properties, CFAllocatorRef allocator, uint32_t options)
kern_return_t IOObjectRelease(io_object_t object)
kern_return_t IOServiceGetMatchingServices(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t *it)
kern_return_t IOObjectRetain(io_object_t object)
io_object_t IOIteratorNext(io_iterator_t it)
@ kIOReturnSuccess
Definition IOKit.h:36
T Get() const
Definition cf_utils.h:101
VkInstance instance
Definition main.cc:64
#define FML_DCHECK(condition)
Definition logging.h:122
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition macros.h:27
CPU usage stats. num_threads is the number of threads owned by the process. It is to be noted that th...
Memory usage stats. dirty_memory_usage is the memory usage (in MB) such that the app uses its physica...
Container for the metrics we collect during each run of Sampler. This currently holds CpuUsageInfo an...
std::optional< CpuUsageInfo > cpu_usage
static void Retain(T instance)
Definition cf_utils.h:18
static constexpr T kNullValue
Definition cf_utils.h:17
static void Release(T instance)
Definition cf_utils.h:19