| 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/fml/platform/darwin/cf_utils.h"
10 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/IOKit.h"
12
13 FLUTTER_ASSERT_ARC
14
15 namespace {
16
17 // RAII holder for `thread_array_t` this is so any early returns in
18 // `ProfilerMetricsIOS::CpuUsage` don't leak them.
19 class MachThreads {
20 public:
21 thread_array_t threads = NULL;
22 mach_msg_type_number_t thread_count = 0;
23
24 MachThreads() = default;
25
26 ~MachThreads() {
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:
33 FML_DISALLOW_COPY_AND_ASSIGN(MachThreads);
34 };
35
36 } // namespace
37
38 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
39 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
40
41 namespace fml {
42
43 /// fml::CFRef retain and release implementations for io_object_t and related types.
44 template <>
45 struct 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
55 namespace flutter {
56 namespace {
57
58 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
59 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
60
61 std::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;
83 if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IntelAccelerator"),
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;
93 if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("sgx"),
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) ==
100 kIOReturnSuccess) {
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
115 std::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
127 ProfileSample ProfilerMetricsIOS::GenerateSample() {
128 return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage(), .gpu_usage = PollGpuUsage()};
129 }
130
131 std::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
184 std::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
FLUTTER_ASSERT_ARC::MachThreads
Definition: profiler_metrics_ios.mm:19
IOObjectRelease
kern_return_t IOObjectRelease(io_object_t object)
IOObjectRetain
kern_return_t IOObjectRetain(io_object_t object)
fml::CFRefTraits< io_object_t >::Release
static void Release(io_object_t instance)
Definition: profiler_metrics_ios.mm:48
FlutterMacros.h
flutter::ProfilerMetricsIOS::GenerateSample
ProfileSample GenerateSample()
Definition: profiler_metrics_ios.mm:127
IOKit.h
kIOReturnSuccess
@ kIOReturnSuccess
Definition: IOKit.h:36
profiler_metrics_ios.h
flutter
Definition: accessibility_bridge.h:27
fml::CFRefTraits< io_object_t >::Retain
static void Retain(io_object_t instance)
Definition: profiler_metrics_ios.mm:47
FLUTTER_ASSERT_ARC::MachThreads::~MachThreads
~MachThreads()
Definition: profiler_metrics_ios.mm:26
fml
Definition: profiler_metrics_ios.mm:41
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)
kIOServicePlane
constexpr const char * kIOServicePlane
Definition: IOKit.h:28
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
IOIteratorNext
io_object_t IOIteratorNext(io_iterator_t it)
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)