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