Flutter Engine
The Flutter Engine
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
13
14namespace {
15
16// RAII holder for `thread_array_t` this is so any early returns in
17// `ProfilerMetricsIOS::CpuUsage` don't leak them.
19 public:
20 thread_array_t threads = NULL;
21 mach_msg_type_number_t thread_count = 0;
22
23 MachThreads() = default;
24
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:
33};
34
35} // namespace
36
37namespace flutter {
38namespace {
39
40#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
41 FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
42
43template <typename T>
44T ClearValue() {
45 return nullptr;
46}
47
48template <>
49io_object_t ClearValue<io_object_t>() {
50 return 0;
51}
52
53template <typename T>
54/// Generic RAII wrapper like unique_ptr but gives access to its handle.
55class 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:
82 T object_;
83 Deleter deleter_;
84};
85
86void DeleteCF(CFMutableDictionaryRef value) {
87 CFRelease(value);
88}
89
90void DeleteIO(io_object_t value) {
92}
93
94std::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);
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);
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);
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
144std::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
157 return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage(), .gpu_usage = PollGpuUsage()};
158}
159
160std::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
213std::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
m reset()
kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t *it)
static const char * kIOServicePlane
Definition: IOKit.h:28
io_object_t io_iterator_t
Definition: IOKit.h:33
const mach_port_t kIOMasterPortDefault
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)
@ kIOReturnSuccess
Definition: IOKit.h:36
io_object_t IOIteratorNext(io_iterator_t it)
uint8_t value
GAsyncResult * result
#define FML_DCHECK(condition)
Definition: logging.h:103
#define FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName)
Definition: macros.h:31
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:27
const myers::Point & get(const myers::Segment &)
#define T
Definition: precompiler.cc:65
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