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/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 }
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:
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);
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);
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);
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 
172  // Add the CPU usage for each thread. It should be noted that there may be some CPU usage missing
173  // from this calculation. If a thread ends between calls to this routine, then its info will be
174  // lost. We could solve this by installing a callback using pthread_key_create. The callback would
175  // report the thread is ending and allow the code to get the CPU usage. But we need to call
176  // pthread_setspecific in each thread to set the key's value to a non-null value for the callback
177  // to work. If we really need this information and if we have a good mechanism for calling
178  // pthread_setspecific in every thread, then we can include that value in the CPU usage.
179  for (mach_msg_type_number_t i = 0; i < mach_threads.thread_count; i++) {
180  thread_basic_info_data_t basic_thread_info;
181  mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
182  kernel_return_code =
183  thread_info(mach_threads.threads[i], THREAD_BASIC_INFO,
184  reinterpret_cast<thread_info_t>(&basic_thread_info), &thread_info_count);
185  if (kernel_return_code != KERN_SUCCESS) {
186  FML_LOG(ERROR) << "Error retrieving thread information: "
187  << mach_error_string(kernel_return_code);
188  return std::nullopt;
189  }
190  const double current_thread_cpu_usage =
191  basic_thread_info.cpu_usage / static_cast<float>(TH_USAGE_SCALE);
192  total_cpu_usage += current_thread_cpu_usage;
193  }
194 
195  flutter::CpuUsageInfo cpu_usage_info = {.num_threads = mach_threads.thread_count,
196  .total_cpu_usage = total_cpu_usage * 100.0};
197  return cpu_usage_info;
198 }
199 
200 std::optional<MemoryUsageInfo> ProfilerMetricsIOS::MemoryUsage() {
201  kern_return_t kernel_return_code;
202  task_vm_info_data_t task_memory_info;
203  mach_msg_type_number_t task_memory_info_count = TASK_VM_INFO_COUNT;
204 
205  kernel_return_code =
206  task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast<task_info_t>(&task_memory_info),
207  &task_memory_info_count);
208  if (kernel_return_code != KERN_SUCCESS) {
209  FML_LOG(ERROR) << " Error retrieving task memory information: "
210  << mach_error_string(kernel_return_code);
211  return std::nullopt;
212  }
213 
214  // `phys_footprint` is Apple's recommended way to measure app's memory usage. It provides the
215  // best approximate to xcode memory gauge. According to its source code explanation, the physical
216  // footprint mainly consists of app's internal memory data and IOKit mappings. `resident_size`
217  // is the total physical memory used by the app, so we simply do `resident_size - phys_footprint`
218  // to obtain the shared memory usage.
219  const double dirty_memory_usage =
220  static_cast<double>(task_memory_info.phys_footprint) / 1024.0 / 1024.0;
221  const double owned_shared_memory_usage =
222  static_cast<double>(task_memory_info.resident_size) / 1024.0 / 1024.0 - dirty_memory_usage;
223  flutter::MemoryUsageInfo memory_usage_info = {
224  .dirty_memory_usage = dirty_memory_usage,
225  .owned_shared_memory_usage = owned_shared_memory_usage};
226  return memory_usage_info;
227 }
228 
229 } // namespace flutter
const mach_port_t kIOMasterPortDefault
#define FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName)
Definition: macros.h:31
kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef *properties, CFAllocatorRef allocator, uint32_t options)
CPU usage stats. num_threads is the number of threads owned by the process. It is to be noted that th...
#define FML_LOG(severity)
Definition: logging.h:65
kern_return_t IOServiceGetMatchingServices(mach_port_t master, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t *it)
Memory usage stats. dirty_memory_usage is the the memory usage (in MB) such that the app uses its phy...
static const char * kIOServicePlane
Definition: IOKit.h:28
Container for the metrics we collect during each run of Sampler. This currently holds CpuUsageInfo an...
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
uint8_t value
CFMutableDictionaryRef IOServiceNameMatching(const char *name) CF_RETURNS_RETAINED
kern_return_t IOObjectRelease(io_object_t object)
std::optional< CpuUsageInfo > cpu_usage
io_object_t IOIteratorNext(io_iterator_t it)
#define FML_CHECK(condition)
Definition: logging.h:68
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: macros.h:27