diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 07d1c4b73c346..728077a3de620 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -903,6 +903,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index d6ebeb35e9afb..e8d1c67beed90 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -141,13 +141,18 @@ source_set("flutter_framework_source") { public_configs = [ "//flutter:config" ] libs = [ + "AudioToolbox.framework", "CoreMedia.framework", "CoreVideo.framework", - "UIKit.framework", "OpenGLES.framework", - "AudioToolbox.framework", "QuartzCore.framework", + "UIKit.framework", ] + if (flutter_runtime_mode == "profile" || flutter_runtime_mode == "debug") { + # This is required by the profiler_metrics_ios.mm to get GPU statistics. + # Usage in release builds will cause rejection from the App Store. + libs += [ "IOKit.framework" ] + } } ios_test_flutter_path = rebase_path("$root_out_dir/libios_test_flutter.dylib") diff --git a/shell/platform/darwin/ios/framework/Source/IOKit.h b/shell/platform/darwin/ios/framework/Source/IOKit.h new file mode 100644 index 0000000000000..6f95655ca407c --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/IOKit.h @@ -0,0 +1,108 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// These declarations are an amalgamation of different headers whose +// symbols exist in IOKit.framework. The headers have been removed +// from the iOS SDKs but all the functions are documented here: +// * https://developer.apple.com/documentation/iokit/iokitlib_h?language=objc +// * https://developer.apple.com/documentation/iokit/iokit_functions?language=objc +// * file:///Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/IOKit.framework/Versions/A/Headers/IOKitLib.h + +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \ + FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_IOKIT_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_IOKIT_H_ + +#if __cplusplus +extern "C" { +#endif // __cplusplus + +#include +#include +#include + +#define IOKIT +#include + +static const char* kIOServicePlane = "IOService"; + +typedef io_object_t io_registry_entry_t; +typedef io_object_t io_service_t; +typedef io_object_t io_connect_t; +typedef io_object_t io_iterator_t; + +enum { + kIOReturnSuccess = 0, +}; + +extern const mach_port_t kIOMasterPortDefault; + +kern_return_t IOObjectRetain(io_object_t object); +kern_return_t IOObjectRelease(io_object_t object); +boolean_t IOObjectConformsTo(io_object_t object, const io_name_t name); +uint32_t IOObjectGetKernelRetainCount(io_object_t object); +kern_return_t IOObjectGetClass(io_object_t object, io_name_t name); +CFStringRef IOObjectCopyClass(io_object_t object); +CFStringRef IOObjectCopySuperclassForClass(CFStringRef name); +CFStringRef IOObjectCopyBundleIdentifierForClass(CFStringRef name); + +io_registry_entry_t IORegistryGetRootEntry(mach_port_t master); +kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name); +kern_return_t IORegistryEntryGetRegistryEntryID(io_registry_entry_t entry, + uint64_t* entryID); +kern_return_t IORegistryEntryGetPath(io_registry_entry_t entry, + const io_name_t plane, + io_string_t path); +kern_return_t IORegistryEntryGetProperty(io_registry_entry_t entry, + const io_name_t name, + io_struct_inband_t buffer, + uint32_t* size); +kern_return_t IORegistryEntryCreateCFProperties( + io_registry_entry_t entry, + CFMutableDictionaryRef* properties, + CFAllocatorRef allocator, + uint32_t options); +CFTypeRef IORegistryEntryCreateCFProperty(io_registry_entry_t entry, + CFStringRef key, + CFAllocatorRef allocator, + uint32_t options); +kern_return_t IORegistryEntrySetCFProperties(io_registry_entry_t entry, + CFTypeRef properties); + +kern_return_t IORegistryCreateIterator(mach_port_t master, + const io_name_t plane, + uint32_t options, + io_iterator_t* it); +kern_return_t IORegistryEntryCreateIterator(io_registry_entry_t entry, + const io_name_t plane, + uint32_t options, + io_iterator_t* it); +kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, + const io_name_t plane, + io_iterator_t* it); +kern_return_t IORegistryEntryGetParentIterator(io_registry_entry_t entry, + const io_name_t plane, + io_iterator_t* it); +io_object_t IOIteratorNext(io_iterator_t it); +boolean_t IOIteratorIsValid(io_iterator_t it); +void IOIteratorReset(io_iterator_t it); + +CFMutableDictionaryRef IOServiceMatching(const char* name) CF_RETURNS_RETAINED; +CFMutableDictionaryRef IOServiceNameMatching(const char* name) + CF_RETURNS_RETAINED; +io_service_t IOServiceGetMatchingService(mach_port_t master, + CFDictionaryRef matching + CF_RELEASES_ARGUMENT); +kern_return_t IOServiceGetMatchingServices(mach_port_t master, + CFDictionaryRef matching + CF_RELEASES_ARGUMENT, + io_iterator_t* it); + +#if __cplusplus +} +#endif // __cplusplus + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_IOKIT_H_ +#endif // defined(FLUTTER_RUNTIME_MODE_DEBUG) || + // defined(FLUTTER_RUNTIME_MODE_PROFILE) diff --git a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm index db223ff2fb387..6433b15299ccf 100644 --- a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm @@ -3,6 +3,8 @@ // found in the LICENSE file. #include "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h" +#import +#import "IOKit.h" namespace { @@ -28,9 +30,126 @@ } namespace flutter { +namespace { + +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \ + FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE + +template +T ClearValue() { + return nullptr; +} + +template <> +io_object_t ClearValue() { + return 0; +} + +template +/// Generic RAII wrapper like unique_ptr but gives access to its handle. +class Scoped { + public: + typedef void (*Deleter)(T); + explicit Scoped(Deleter deleter) : object_(ClearValue()), deleter_(deleter) {} + Scoped(T object, Deleter deleter) : object_(object), deleter_(deleter) {} + ~Scoped() { + if (object_) { + deleter_(object_); + } + } + T* handle() { + if (object_) { + deleter_(object_); + object_ = ClearValue(); + } + return &object_; + } + T get() { return object_; } + void reset(T new_value) { + if (object_) { + deleter_(object_); + } + object_ = new_value; + } + + private: + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(Scoped); + T object_; + Deleter deleter_; +}; + +void DeleteCF(CFMutableDictionaryRef value) { + CFRelease(value); +} + +void DeleteIO(io_object_t value) { + IOObjectRelease(value); +} + +std::optional FindGpuUsageInfo(io_iterator_t iterator) { + for (Scoped regEntry(IOIteratorNext(iterator), DeleteIO); regEntry.get(); + regEntry.reset(IOIteratorNext(iterator))) { + Scoped serviceDictionary(DeleteCF); + if (IORegistryEntryCreateCFProperties(regEntry.get(), serviceDictionary.handle(), + kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) { + continue; + } + + NSDictionary* dictionary = + ((__bridge NSDictionary*)serviceDictionary.get())[@"PerformanceStatistics"]; + NSNumber* utilization = dictionary[@"Device Utilization %"]; + if (utilization) { + return (GpuUsageInfo){.percent_usage = [utilization doubleValue]}; + } + } + return std::nullopt; +} + +[[maybe_unused]] std::optional FindSimulatorGpuUsageInfo() { + Scoped iterator(DeleteIO); + if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IntelAccelerator"), + iterator.handle()) == kIOReturnSuccess) { + return FindGpuUsageInfo(iterator.get()); + } + return std::nullopt; +} + +[[maybe_unused]] std::optional FindDeviceGpuUsageInfo() { + Scoped iterator(DeleteIO); + if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("sgx"), + iterator.handle()) == kIOReturnSuccess) { + for (Scoped regEntry(IOIteratorNext(iterator.get()), DeleteIO); + regEntry.get(); regEntry.reset(IOIteratorNext(iterator.get()))) { + Scoped innerIterator(DeleteIO); + if (IORegistryEntryGetChildIterator(regEntry.get(), kIOServicePlane, + innerIterator.handle()) == kIOReturnSuccess) { + std::optional result = FindGpuUsageInfo(innerIterator.get()); + if (result.has_value()) { + return result; + } + } + } + } + return std::nullopt; +} + +#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || + // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE + +std::optional PollGpuUsage() { +#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE || \ + FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_JIT_RELEASE) + return std::nullopt; +#elif TARGET_IPHONE_SIMULATOR + return FindSimulatorGpuUsageInfo(); +#elif TARGET_OS_IOS + return FindDeviceGpuUsageInfo(); +#endif // TARGET_IPHONE_SIMULATOR +} +} // namespace ProfileSample ProfilerMetricsIOS::GenerateSample() { - return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage()}; + return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage(), .gpu_usage = PollGpuUsage()}; } std::optional ProfilerMetricsIOS::CpuUsage() { diff --git a/shell/profiling/sampling_profiler.cc b/shell/profiling/sampling_profiler.cc index 1f5410fa8a12a..790d70f91ec4f 100644 --- a/shell/profiling/sampling_profiler.cc +++ b/shell/profiling/sampling_profiler.cc @@ -54,6 +54,12 @@ void SamplingProfiler::SampleRepeatedly(fml::TimeDelta task_delay) const { "owned_shared_memory_usage", owned_shared_memory_usage.c_str()); } + if (usage.gpu_usage) { + std::string gpu_usage = + std::to_string(usage.gpu_usage->percent_usage); + TRACE_EVENT_INSTANT1("flutter::profiling", "GpuUsage", "gpu_usage", + gpu_usage.c_str()); + } profiler->SampleRepeatedly(task_delay); }, task_delay); diff --git a/shell/profiling/sampling_profiler.h b/shell/profiling/sampling_profiler.h index 598443d037ac0..146454d3fc2ea 100644 --- a/shell/profiling/sampling_profiler.h +++ b/shell/profiling/sampling_profiler.h @@ -42,6 +42,13 @@ struct MemoryUsageInfo { double owned_shared_memory_usage; }; +/** + * @brief Polled information related to the usage of the GPU. + */ +struct GpuUsageInfo { + double percent_usage; +}; + /** * @brief Container for the metrics we collect during each run of `Sampler`. * This currently holds `CpuUsageInfo` and `MemoryUsageInfo` but the intent @@ -52,6 +59,7 @@ struct MemoryUsageInfo { struct ProfileSample { std::optional cpu_usage; std::optional memory_usage; + std::optional gpu_usage; }; /**