Flutter iOS Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
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 
6 
7 #import <Foundation/Foundation.h>
8 
9 #import "flutter/fml/platform/darwin/cf_utils.h"
12 
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 
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;
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;
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) ==
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 
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)