| Flutter iOS Embedder |

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
5 #import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
6
7 #import <Foundation/Foundation.h>
8
9 #import "flutter/shell/platform/darwin/ios/framework/Source/IOKit.h"
10
11 namespace {
12
13 // RAII holder for `thread_array_t` this is so any early returns in
14 // `ProfilerMetricsIOS::CpuUsage` don't leak them.
15 class MachThreads {
16 public:
17 thread_array_t threads = NULL;
18 mach_msg_type_number_t thread_count = 0;
19
20 MachThreads() = default;
21
22 ~MachThreads() {
23 kern_return_t kernel_return_code = vm_deallocate(
24 mach_task_self(), reinterpret_cast<vm_offset_t>(threads), thread_count * sizeof(thread_t));
25 FML_CHECK(kernel_return_code == KERN_SUCCESS) << "Failed to deallocate thread infos.";
26 }
27
28 private:
29 FML_DISALLOW_COPY_AND_ASSIGN(MachThreads);
30 };
31
32 } // namespace
33
34 namespace flutter {
35 namespace {
36
37 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
38 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
39
40 template <typename T>
41 T ClearValue() {
42 return nullptr;
43 }
44
45 template <>
46 io_object_t ClearValue<io_object_t>() {
47 return 0;
48 }
49
50 template <typename T>
51 /// Generic RAII wrapper like unique_ptr but gives access to its handle.
52 class Scoped {
53 public:
54 typedef void (*Deleter)(T);
55 explicit Scoped(Deleter deleter) : object_(ClearValue<T>()), deleter_(deleter) {}
56 Scoped(T object, Deleter deleter) : object_(object), deleter_(deleter) {}
57 ~Scoped() {
58 if (object_) {
59 deleter_(object_);
60 }
61 }
62 T* handle() {
63 if (object_) {
64 deleter_(object_);
65 object_ = ClearValue<T>();
66 }
67 return &object_;
68 }
69 T get() { return object_; }
70 void reset(T new_value) {
71 if (object_) {
72 deleter_(object_);
73 }
74 object_ = new_value;
75 }
76
77 private:
78 FML_DISALLOW_COPY_ASSIGN_AND_MOVE(Scoped);
79 T object_;
80 Deleter deleter_;
81 };
82
83 void DeleteCF(CFMutableDictionaryRef value) {
84 CFRelease(value);
85 }
86
87 void DeleteIO(io_object_t value) {
88 IOObjectRelease(value);
89 }
90
91 std::optional<GpuUsageInfo> FindGpuUsageInfo(io_iterator_t iterator) {
92 for (Scoped<io_registry_entry_t> regEntry(IOIteratorNext(iterator), DeleteIO); regEntry.get();
93 regEntry.reset(IOIteratorNext(iterator))) {
94 Scoped<CFMutableDictionaryRef> serviceDictionary(DeleteCF);
95 if (IORegistryEntryCreateCFProperties(regEntry.get(), serviceDictionary.handle(),
96 kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
97 continue;
98 }
99
100 NSDictionary* dictionary =
101 ((__bridge NSDictionary*)serviceDictionary.get())[@"PerformanceStatistics"];
102 NSNumber* utilization = dictionary[@"Device Utilization %"];
103 if (utilization) {
104 return (GpuUsageInfo){.percent_usage = [utilization doubleValue]};
105 }
106 }
107 return std::nullopt;
108 }
109
110 [[maybe_unused]] std::optional<GpuUsageInfo> FindSimulatorGpuUsageInfo() {
111 Scoped<io_iterator_t> iterator(DeleteIO);
112 if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IntelAccelerator"),
113 iterator.handle()) == kIOReturnSuccess) {
114 return FindGpuUsageInfo(iterator.get());
115 }
116 return std::nullopt;
117 }
118
119 [[maybe_unused]] std::optional<GpuUsageInfo> FindDeviceGpuUsageInfo() {
120 Scoped<io_iterator_t> iterator(DeleteIO);
121 if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("sgx"),
122 iterator.handle()) == kIOReturnSuccess) {
123 for (Scoped<io_registry_entry_t> regEntry(IOIteratorNext(iterator.get()), DeleteIO);
124 regEntry.get(); regEntry.reset(IOIteratorNext(iterator.get()))) {
125 Scoped<io_iterator_t> innerIterator(DeleteIO);
126 if (IORegistryEntryGetChildIterator(regEntry.get(), kIOServicePlane,
127 innerIterator.handle()) == kIOReturnSuccess) {
128 std::optional<GpuUsageInfo> result = FindGpuUsageInfo(innerIterator.get());
129 if (result.has_value()) {
130 return result;
131 }
132 }
133 }
134 }
135 return std::nullopt;
136 }
137
138 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG ||
139 // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
140
141 std::optional<GpuUsageInfo> PollGpuUsage() {
142 #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE || \
143 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_JIT_RELEASE)
144 return std::nullopt;
145 #elif TARGET_IPHONE_SIMULATOR
146 return FindSimulatorGpuUsageInfo();
147 #elif TARGET_OS_IOS
148 return FindDeviceGpuUsageInfo();
149 #endif // TARGET_IPHONE_SIMULATOR
150 }
151 } // namespace
152
153 ProfileSample ProfilerMetricsIOS::GenerateSample() {
154 return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage(), .gpu_usage = PollGpuUsage()};
155 }
156
157 std::optional<CpuUsageInfo> ProfilerMetricsIOS::CpuUsage() {
158 kern_return_t kernel_return_code;
159 MachThreads mach_threads = MachThreads();
160
161 // Get threads in the task
162 kernel_return_code =
163 task_threads(mach_task_self(), &mach_threads.threads, &mach_threads.thread_count);
164 if (kernel_return_code != KERN_SUCCESS) {
165 FML_LOG(ERROR) << "Error retrieving task information: "
166 << mach_error_string(kernel_return_code);
167 return std::nullopt;
168 }
169
170 double total_cpu_usage = 0.0;
171 uint32_t num_threads = mach_threads.thread_count;
172
173 // Add the CPU usage for each thread. It should be noted that there may be some CPU usage missing
174 // from this calculation. If a thread ends between calls to this routine, then its info will be
175 // lost. We could solve this by installing a callback using pthread_key_create. The callback would
176 // report the thread is ending and allow the code to get the CPU usage. But we need to call
177 // pthread_setspecific in each thread to set the key's value to a non-null value for the callback
178 // to work. If we really need this information and if we have a good mechanism for calling
179 // pthread_setspecific in every thread, then we can include that value in the CPU usage.
180 for (mach_msg_type_number_t i = 0; i < mach_threads.thread_count; i++) {
181 thread_basic_info_data_t basic_thread_info;
182 mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
183 kernel_return_code =
184 thread_info(mach_threads.threads[i], THREAD_BASIC_INFO,
185 reinterpret_cast<thread_info_t>(&basic_thread_info), &thread_info_count);
186 switch (kernel_return_code) {
187 case KERN_SUCCESS: {
188 const double current_thread_cpu_usage =
189 basic_thread_info.cpu_usage / static_cast<float>(TH_USAGE_SCALE);
190 total_cpu_usage += current_thread_cpu_usage;
191 break;
192 }
193 case MACH_SEND_TIMEOUT:
194 case MACH_SEND_TIMED_OUT:
195 case MACH_SEND_INVALID_DEST:
196 // Ignore as this thread been destroyed. The possible return codes are not really well
197 // documented. This handling is inspired from the following sources:
198 // - https://opensource.apple.com/source/xnu/xnu-4903.221.2/tests/task_inspect.c.auto.html
199 // - https://github.com/apple/swift-corelibs-libdispatch/blob/main/src/queue.c#L6617
200 num_threads--;
201 break;
202 default:
203 FML_LOG(ERROR) << "Error retrieving thread information: "
204 << mach_error_string(kernel_return_code);
205 return std::nullopt;
206 }
207 }
208
209 flutter::CpuUsageInfo cpu_usage_info = {.num_threads = num_threads,
210 .total_cpu_usage = total_cpu_usage * 100.0};
211 return cpu_usage_info;
212 }
213
214 std::optional<MemoryUsageInfo> ProfilerMetricsIOS::MemoryUsage() {
215 kern_return_t kernel_return_code;
216 task_vm_info_data_t task_memory_info;
217 mach_msg_type_number_t task_memory_info_count = TASK_VM_INFO_COUNT;
218
219 kernel_return_code =
220 task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&task_memory_info),
221 &task_memory_info_count);
222 if (kernel_return_code != KERN_SUCCESS) {
223 FML_LOG(ERROR) << " Error retrieving task memory information: "
224 << mach_error_string(kernel_return_code);
225 return std::nullopt;
226 }
227
228 // `phys_footprint` is Apple's recommended way to measure app's memory usage. It provides the
229 // best approximate to xcode memory gauge. According to its source code explanation, the physical
230 // footprint mainly consists of app's internal memory data and IOKit mappings. `resident_size`
231 // is the total physical memory used by the app, so we simply do `resident_size - phys_footprint`
232 // to obtain the shared memory usage.
233 const double dirty_memory_usage =
234 static_cast<double>(task_memory_info.phys_footprint) / 1024.0 / 1024.0;
235 const double owned_shared_memory_usage =
236 static_cast<double>(task_memory_info.resident_size) / 1024.0 / 1024.0 - dirty_memory_usage;
237 flutter::MemoryUsageInfo memory_usage_info = {
238 .dirty_memory_usage = dirty_memory_usage,
239 .owned_shared_memory_usage = owned_shared_memory_usage};
240 return memory_usage_info;
241 }
242
243 } // namespace flutter
IOObjectRelease
kern_return_t IOObjectRelease(io_object_t object)
flutter::ProfilerMetricsIOS::GenerateSample
ProfileSample GenerateSample()
Definition: profiler_metrics_ios.mm:153
IOKit.h
kIOReturnSuccess
@ kIOReturnSuccess
Definition: IOKit.h:36
profiler_metrics_ios.h
flutter
Definition: accessibility_bridge.h:28
kIOMasterPortDefault
const mach_port_t kIOMasterPortDefault
IORegistryEntryGetChildIterator
kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it)
IOServiceNameMatching
CFMutableDictionaryRef IOServiceNameMatching(const char *name) CF_RETURNS_RETAINED
IOServiceGetMatchingServices
kern_return_t IOServiceGetMatchingServices(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t *it)
IOIteratorNext
io_object_t IOIteratorNext(io_iterator_t it)
kIOServicePlane
static const char * kIOServicePlane
Definition: IOKit.h:28
io_iterator_t
io_object_t io_iterator_t
Definition: IOKit.h:33
IORegistryEntryCreateCFProperties
kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef *properties, CFAllocatorRef allocator, uint32_t options)