Skip to content

Commit ca2a370

Browse files
authored
started polling the gpu usage (flutter#18752)
1 parent c00882a commit ca2a370

File tree

6 files changed

+250
-3
lines changed

6 files changed

+250
-3
lines changed

ci/licenses_golden/licenses_flutter

+1
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm
907907
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
908908
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm
909909
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h
910+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/IOKit.h
910911
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
911912
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
912913
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm

shell/platform/darwin/ios/BUILD.gn

+7-2
Original file line numberDiff line numberDiff line change
@@ -141,13 +141,18 @@ source_set("flutter_framework_source") {
141141
public_configs = [ "//flutter:config" ]
142142

143143
libs = [
144+
"AudioToolbox.framework",
144145
"CoreMedia.framework",
145146
"CoreVideo.framework",
146-
"UIKit.framework",
147147
"OpenGLES.framework",
148-
"AudioToolbox.framework",
149148
"QuartzCore.framework",
149+
"UIKit.framework",
150150
]
151+
if (flutter_runtime_mode == "profile" || flutter_runtime_mode == "debug") {
152+
# This is required by the profiler_metrics_ios.mm to get GPU statistics.
153+
# Usage in release builds will cause rejection from the App Store.
154+
libs += [ "IOKit.framework" ]
155+
}
151156
}
152157

153158
ios_test_flutter_path = rebase_path("$root_out_dir/libios_test_flutter.dylib")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
// These declarations are an amalgamation of different headers whose
6+
// symbols exist in IOKit.framework. The headers have been removed
7+
// from the iOS SDKs but all the functions are documented here:
8+
// * https://developer.apple.com/documentation/iokit/iokitlib_h?language=objc
9+
// * https://developer.apple.com/documentation/iokit/iokit_functions?language=objc
10+
// * file:///Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/IOKit.framework/Versions/A/Headers/IOKitLib.h
11+
12+
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
13+
FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
14+
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_IOKIT_H_
15+
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_IOKIT_H_
16+
17+
#if __cplusplus
18+
extern "C" {
19+
#endif // __cplusplus
20+
21+
#include <CoreFoundation/CoreFoundation.h>
22+
#include <mach/mach.h>
23+
#include <stdint.h>
24+
25+
#define IOKIT
26+
#include <device/device_types.h>
27+
28+
static const char* kIOServicePlane = "IOService";
29+
30+
typedef io_object_t io_registry_entry_t;
31+
typedef io_object_t io_service_t;
32+
typedef io_object_t io_connect_t;
33+
typedef io_object_t io_iterator_t;
34+
35+
enum {
36+
kIOReturnSuccess = 0,
37+
};
38+
39+
extern const mach_port_t kIOMasterPortDefault;
40+
41+
kern_return_t IOObjectRetain(io_object_t object);
42+
kern_return_t IOObjectRelease(io_object_t object);
43+
boolean_t IOObjectConformsTo(io_object_t object, const io_name_t name);
44+
uint32_t IOObjectGetKernelRetainCount(io_object_t object);
45+
kern_return_t IOObjectGetClass(io_object_t object, io_name_t name);
46+
CFStringRef IOObjectCopyClass(io_object_t object);
47+
CFStringRef IOObjectCopySuperclassForClass(CFStringRef name);
48+
CFStringRef IOObjectCopyBundleIdentifierForClass(CFStringRef name);
49+
50+
io_registry_entry_t IORegistryGetRootEntry(mach_port_t master);
51+
kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name);
52+
kern_return_t IORegistryEntryGetRegistryEntryID(io_registry_entry_t entry,
53+
uint64_t* entryID);
54+
kern_return_t IORegistryEntryGetPath(io_registry_entry_t entry,
55+
const io_name_t plane,
56+
io_string_t path);
57+
kern_return_t IORegistryEntryGetProperty(io_registry_entry_t entry,
58+
const io_name_t name,
59+
io_struct_inband_t buffer,
60+
uint32_t* size);
61+
kern_return_t IORegistryEntryCreateCFProperties(
62+
io_registry_entry_t entry,
63+
CFMutableDictionaryRef* properties,
64+
CFAllocatorRef allocator,
65+
uint32_t options);
66+
CFTypeRef IORegistryEntryCreateCFProperty(io_registry_entry_t entry,
67+
CFStringRef key,
68+
CFAllocatorRef allocator,
69+
uint32_t options);
70+
kern_return_t IORegistryEntrySetCFProperties(io_registry_entry_t entry,
71+
CFTypeRef properties);
72+
73+
kern_return_t IORegistryCreateIterator(mach_port_t master,
74+
const io_name_t plane,
75+
uint32_t options,
76+
io_iterator_t* it);
77+
kern_return_t IORegistryEntryCreateIterator(io_registry_entry_t entry,
78+
const io_name_t plane,
79+
uint32_t options,
80+
io_iterator_t* it);
81+
kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry,
82+
const io_name_t plane,
83+
io_iterator_t* it);
84+
kern_return_t IORegistryEntryGetParentIterator(io_registry_entry_t entry,
85+
const io_name_t plane,
86+
io_iterator_t* it);
87+
io_object_t IOIteratorNext(io_iterator_t it);
88+
boolean_t IOIteratorIsValid(io_iterator_t it);
89+
void IOIteratorReset(io_iterator_t it);
90+
91+
CFMutableDictionaryRef IOServiceMatching(const char* name) CF_RETURNS_RETAINED;
92+
CFMutableDictionaryRef IOServiceNameMatching(const char* name)
93+
CF_RETURNS_RETAINED;
94+
io_service_t IOServiceGetMatchingService(mach_port_t master,
95+
CFDictionaryRef matching
96+
CF_RELEASES_ARGUMENT);
97+
kern_return_t IOServiceGetMatchingServices(mach_port_t master,
98+
CFDictionaryRef matching
99+
CF_RELEASES_ARGUMENT,
100+
io_iterator_t* it);
101+
102+
#if __cplusplus
103+
}
104+
#endif // __cplusplus
105+
106+
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_IOKIT_H_
107+
#endif // defined(FLUTTER_RUNTIME_MODE_DEBUG) ||
108+
// defined(FLUTTER_RUNTIME_MODE_PROFILE)

shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm

+120-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// found in the LICENSE file.
44

55
#include "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
6+
#import <Foundation/Foundation.h>
7+
#import "IOKit.h"
68

79
namespace {
810

@@ -28,9 +30,126 @@
2830
}
2931

3032
namespace flutter {
33+
namespace {
34+
35+
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG || \
36+
FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
37+
38+
template <typename T>
39+
T ClearValue() {
40+
return nullptr;
41+
}
42+
43+
template <>
44+
io_object_t ClearValue<io_object_t>() {
45+
return 0;
46+
}
47+
48+
template <typename T>
49+
/// Generic RAII wrapper like unique_ptr but gives access to its handle.
50+
class Scoped {
51+
public:
52+
typedef void (*Deleter)(T);
53+
explicit Scoped(Deleter deleter) : object_(ClearValue<T>()), deleter_(deleter) {}
54+
Scoped(T object, Deleter deleter) : object_(object), deleter_(deleter) {}
55+
~Scoped() {
56+
if (object_) {
57+
deleter_(object_);
58+
}
59+
}
60+
T* handle() {
61+
if (object_) {
62+
deleter_(object_);
63+
object_ = ClearValue<T>();
64+
}
65+
return &object_;
66+
}
67+
T get() { return object_; }
68+
void reset(T new_value) {
69+
if (object_) {
70+
deleter_(object_);
71+
}
72+
object_ = new_value;
73+
}
74+
75+
private:
76+
FML_DISALLOW_COPY_ASSIGN_AND_MOVE(Scoped);
77+
T object_;
78+
Deleter deleter_;
79+
};
80+
81+
void DeleteCF(CFMutableDictionaryRef value) {
82+
CFRelease(value);
83+
}
84+
85+
void DeleteIO(io_object_t value) {
86+
IOObjectRelease(value);
87+
}
88+
89+
std::optional<GpuUsageInfo> FindGpuUsageInfo(io_iterator_t iterator) {
90+
for (Scoped<io_registry_entry_t> regEntry(IOIteratorNext(iterator), DeleteIO); regEntry.get();
91+
regEntry.reset(IOIteratorNext(iterator))) {
92+
Scoped<CFMutableDictionaryRef> serviceDictionary(DeleteCF);
93+
if (IORegistryEntryCreateCFProperties(regEntry.get(), serviceDictionary.handle(),
94+
kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
95+
continue;
96+
}
97+
98+
NSDictionary* dictionary =
99+
((__bridge NSDictionary*)serviceDictionary.get())[@"PerformanceStatistics"];
100+
NSNumber* utilization = dictionary[@"Device Utilization %"];
101+
if (utilization) {
102+
return (GpuUsageInfo){.percent_usage = [utilization doubleValue]};
103+
}
104+
}
105+
return std::nullopt;
106+
}
107+
108+
[[maybe_unused]] std::optional<GpuUsageInfo> FindSimulatorGpuUsageInfo() {
109+
Scoped<io_iterator_t> iterator(DeleteIO);
110+
if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IntelAccelerator"),
111+
iterator.handle()) == kIOReturnSuccess) {
112+
return FindGpuUsageInfo(iterator.get());
113+
}
114+
return std::nullopt;
115+
}
116+
117+
[[maybe_unused]] std::optional<GpuUsageInfo> FindDeviceGpuUsageInfo() {
118+
Scoped<io_iterator_t> iterator(DeleteIO);
119+
if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("sgx"),
120+
iterator.handle()) == kIOReturnSuccess) {
121+
for (Scoped<io_registry_entry_t> regEntry(IOIteratorNext(iterator.get()), DeleteIO);
122+
regEntry.get(); regEntry.reset(IOIteratorNext(iterator.get()))) {
123+
Scoped<io_iterator_t> innerIterator(DeleteIO);
124+
if (IORegistryEntryGetChildIterator(regEntry.get(), kIOServicePlane,
125+
innerIterator.handle()) == kIOReturnSuccess) {
126+
std::optional<GpuUsageInfo> result = FindGpuUsageInfo(innerIterator.get());
127+
if (result.has_value()) {
128+
return result;
129+
}
130+
}
131+
}
132+
}
133+
return std::nullopt;
134+
}
135+
136+
#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG ||
137+
// FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE
138+
139+
std::optional<GpuUsageInfo> PollGpuUsage() {
140+
#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_RELEASE || \
141+
FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_JIT_RELEASE)
142+
return std::nullopt;
143+
#elif TARGET_IPHONE_SIMULATOR
144+
return FindSimulatorGpuUsageInfo();
145+
#elif TARGET_OS_IOS
146+
return FindDeviceGpuUsageInfo();
147+
#endif // TARGET_IPHONE_SIMULATOR
148+
}
149+
} // namespace
31150

32151
ProfileSample ProfilerMetricsIOS::GenerateSample() {
33-
return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage()};
152+
return {.cpu_usage = CpuUsage(), .memory_usage = MemoryUsage(), .gpu_usage = PollGpuUsage()};
34153
}
35154

36155
std::optional<CpuUsageInfo> ProfilerMetricsIOS::CpuUsage() {

shell/profiling/sampling_profiler.cc

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ void SamplingProfiler::SampleRepeatedly(fml::TimeDelta task_delay) const {
5454
"owned_shared_memory_usage",
5555
owned_shared_memory_usage.c_str());
5656
}
57+
if (usage.gpu_usage) {
58+
std::string gpu_usage =
59+
std::to_string(usage.gpu_usage->percent_usage);
60+
TRACE_EVENT_INSTANT1("flutter::profiling", "GpuUsage", "gpu_usage",
61+
gpu_usage.c_str());
62+
}
5763
profiler->SampleRepeatedly(task_delay);
5864
},
5965
task_delay);

shell/profiling/sampling_profiler.h

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ struct MemoryUsageInfo {
4242
double owned_shared_memory_usage;
4343
};
4444

45+
/**
46+
* @brief Polled information related to the usage of the GPU.
47+
*/
48+
struct GpuUsageInfo {
49+
double percent_usage;
50+
};
51+
4552
/**
4653
* @brief Container for the metrics we collect during each run of `Sampler`.
4754
* This currently holds `CpuUsageInfo` and `MemoryUsageInfo` but the intent
@@ -52,6 +59,7 @@ struct MemoryUsageInfo {
5259
struct ProfileSample {
5360
std::optional<CpuUsageInfo> cpu_usage;
5461
std::optional<MemoryUsageInfo> memory_usage;
62+
std::optional<GpuUsageInfo> gpu_usage;
5563
};
5664

5765
/**

0 commit comments

Comments
 (0)