Skip to content

Commit ede658e

Browse files
authored
[profiling] CPU Profiling support for iOS (flutter#18087)
See flutter.dev/go/engine-cpu-profiling for details
1 parent d043923 commit ede658e

File tree

13 files changed

+391
-2
lines changed

13 files changed

+391
-2
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_messa
905905
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm
906906
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h
907907
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.mm
908+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h
909+
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm
908910
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h
909911
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm
910912
FILE: ../../../flutter/shell/platform/darwin/ios/framework/module.modulemap
@@ -1223,6 +1225,8 @@ FILE: ../../../flutter/shell/platform/windows/win32_window.cc
12231225
FILE: ../../../flutter/shell/platform/windows/win32_window.h
12241226
FILE: ../../../flutter/shell/platform/windows/win32_window_unittests.cc
12251227
FILE: ../../../flutter/shell/platform/windows/window_state.h
1228+
FILE: ../../../flutter/shell/profiling/sampling_profiler.cc
1229+
FILE: ../../../flutter/shell/profiling/sampling_profiler.h
12261230
FILE: ../../../flutter/shell/version/version.cc
12271231
FILE: ../../../flutter/shell/version/version.h
12281232
FILE: ../../../flutter/sky/packages/flutter_services/lib/empty.dart

fml/trace_event.cc

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,40 @@ void TraceEventInstant0(TraceArg category_group, TraceArg name) {
219219
);
220220
}
221221

222+
void TraceEventInstant1(TraceArg category_group,
223+
TraceArg name,
224+
TraceArg arg1_name,
225+
TraceArg arg1_val) {
226+
const char* arg_names[] = {arg1_name};
227+
const char* arg_values[] = {arg1_val};
228+
FlutterTimelineEvent(name, // label
229+
Dart_TimelineGetMicros(), // timestamp0
230+
0, // timestamp1_or_async_id
231+
Dart_Timeline_Event_Instant, // event type
232+
1, // argument_count
233+
arg_names, // argument_names
234+
arg_values // argument_values
235+
);
236+
}
237+
238+
void TraceEventInstant2(TraceArg category_group,
239+
TraceArg name,
240+
TraceArg arg1_name,
241+
TraceArg arg1_val,
242+
TraceArg arg2_name,
243+
TraceArg arg2_val) {
244+
const char* arg_names[] = {arg1_name, arg2_name};
245+
const char* arg_values[] = {arg1_val, arg2_val};
246+
FlutterTimelineEvent(name, // label
247+
Dart_TimelineGetMicros(), // timestamp0
248+
0, // timestamp1_or_async_id
249+
Dart_Timeline_Event_Instant, // event type
250+
2, // argument_count
251+
arg_names, // argument_names
252+
arg_values // argument_values
253+
);
254+
}
255+
222256
void TraceEventFlowBegin0(TraceArg category_group,
223257
TraceArg name,
224258
TraceIDArg id) {
@@ -322,6 +356,18 @@ void TraceEventAsyncEnd1(TraceArg category_group,
322356

323357
void TraceEventInstant0(TraceArg category_group, TraceArg name) {}
324358

359+
void TraceEventInstant1(TraceArg category_group,
360+
TraceArg name,
361+
TraceArg arg1_name,
362+
TraceArg arg1_val) {}
363+
364+
void TraceEventInstant2(TraceArg category_group,
365+
TraceArg name,
366+
TraceArg arg1_name,
367+
TraceArg arg1_val,
368+
TraceArg arg2_name,
369+
TraceArg arg2_val) {}
370+
325371
void TraceEventFlowBegin0(TraceArg category_group,
326372
TraceArg name,
327373
TraceIDArg id) {}

fml/trace_event.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
#define TRACE_EVENT_ASYNC_BEGIN1(a, b, c, d, e) TRACE_ASYNC_BEGIN(a, b, c, d, e)
2929
#define TRACE_EVENT_ASYNC_END1(a, b, c, d, e) TRACE_ASYNC_END(a, b, c, d, e)
3030
#define TRACE_EVENT_INSTANT0(a, b) TRACE_INSTANT(a, b, TRACE_SCOPE_THREAD)
31+
#define TRACE_EVENT_INSTANT1(a, b, k1, v1) \
32+
TRACE_INSTANT(a, b, TRACE_SCOPE_THREAD, k1, v1)
33+
#define TRACE_EVENT_INSTANT2(a, b, k1, v1, k2, v2) \
34+
TRACE_INSTANT(a, b, TRACE_SCOPE_THREAD, k1, v1, k2, v2)
3135

3236
#endif // defined(OS_FUCHSIA)
3337

@@ -94,6 +98,14 @@
9498
#define TRACE_EVENT_INSTANT0(category_group, name) \
9599
::fml::tracing::TraceEventInstant0(category_group, name);
96100

101+
#define TRACE_EVENT_INSTANT1(category_group, name, arg1_name, arg1_val) \
102+
::fml::tracing::TraceEventInstant1(category_group, name, arg1_name, arg1_val);
103+
104+
#define TRACE_EVENT_INSTANT2(category_group, name, arg1_name, arg1_val, \
105+
arg2_name, arg2_val) \
106+
::fml::tracing::TraceEventInstant2(category_group, name, arg1_name, \
107+
arg1_val, arg2_name, arg2_val);
108+
97109
#define TRACE_FLOW_BEGIN(category, name, id) \
98110
::fml::tracing::TraceEventFlowBegin0(category, name, id);
99111

@@ -272,6 +284,18 @@ void TraceEventAsyncEnd1(TraceArg category_group,
272284

273285
void TraceEventInstant0(TraceArg category_group, TraceArg name);
274286

287+
void TraceEventInstant1(TraceArg category_group,
288+
TraceArg name,
289+
TraceArg arg1_name,
290+
TraceArg arg1_val);
291+
292+
void TraceEventInstant2(TraceArg category_group,
293+
TraceArg name,
294+
TraceArg arg1_name,
295+
TraceArg arg1_val,
296+
TraceArg arg2_name,
297+
TraceArg arg2_val);
298+
275299
void TraceEventFlowBegin0(TraceArg category_group,
276300
TraceArg name,
277301
TraceIDArg id);

shell/common/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ source_set("common") {
126126
"//flutter/fml",
127127
"//flutter/lib/ui",
128128
"//flutter/runtime",
129+
"//flutter/shell/profiling",
129130
"//third_party/dart/runtime:dart_api",
130131
"//third_party/skia",
131132
]

shell/common/thread_host.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ ThreadHost::ThreadHost(std::string name_prefix, uint64_t mask) {
2626
if (mask & ThreadHost::Type::IO) {
2727
io_thread = std::make_unique<fml::Thread>(name_prefix + ".io");
2828
}
29+
30+
if (mask & ThreadHost::Type::Profiler) {
31+
profiler_thread = std::make_unique<fml::Thread>(name_prefix + ".profiler");
32+
}
2933
}
3034

3135
ThreadHost::~ThreadHost() = default;
@@ -35,6 +39,7 @@ void ThreadHost::Reset() {
3539
ui_thread.reset();
3640
raster_thread.reset();
3741
io_thread.reset();
42+
profiler_thread.reset();
3843
}
3944

4045
} // namespace flutter

shell/common/thread_host.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ struct ThreadHost {
1919
UI = 1 << 1,
2020
GPU = 1 << 2,
2121
IO = 1 << 3,
22+
Profiler = 1 << 4,
2223
};
2324

2425
std::unique_ptr<fml::Thread> platform_thread;
2526
std::unique_ptr<fml::Thread> ui_thread;
2627
std::unique_ptr<fml::Thread> raster_thread;
2728
std::unique_ptr<fml::Thread> io_thread;
29+
std::unique_ptr<fml::Thread> profiler_thread;
2830

2931
ThreadHost();
3032

shell/platform/darwin/ios/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ source_set("flutter_framework_source") {
7979
"framework/Source/platform_message_response_darwin.mm",
8080
"framework/Source/platform_message_router.h",
8181
"framework/Source/platform_message_router.mm",
82+
"framework/Source/profiler_metrics_ios.h",
83+
"framework/Source/profiler_metrics_ios.mm",
8284
"framework/Source/vsync_waiter_ios.h",
8385
"framework/Source/vsync_waiter_ios.mm",
8486
"ios_context.h",
@@ -131,6 +133,7 @@ source_set("flutter_framework_source") {
131133
"//flutter/shell/common",
132134
"//flutter/shell/platform/darwin/common",
133135
"//flutter/shell/platform/darwin/common:framework_shared",
136+
"//flutter/shell/profiling:profiling",
134137
"//third_party/skia",
135138
]
136139

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
2525
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
2626
#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
27+
#import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
2728
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
2829
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
2930
#include "flutter/shell/platform/darwin/ios/rendering_api_selection.h"
31+
#include "flutter/shell/profiling/sampling_profiler.h"
3032

3133
NSString* const FlutterDefaultDartEntrypoint = nil;
34+
static constexpr int kNumProfilerSamplesPerSec = 5;
3235

3336
@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
3437
@property(nonatomic, assign) FlutterEngine* flutterEngine;
@@ -56,6 +59,8 @@ @implementation FlutterEngine {
5659
fml::scoped_nsobject<FlutterObservatoryPublisher> _publisher;
5760

5861
std::unique_ptr<flutter::FlutterPlatformViewsController> _platformViewsController;
62+
std::unique_ptr<flutter::ProfilerMetricsIOS> _profiler_metrics;
63+
std::unique_ptr<flutter::SamplingProfiler> _profiler;
5964

6065
// Channels
6166
fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
@@ -262,6 +267,7 @@ - (void)destroyContext {
262267
[self resetChannels];
263268
self.isolateId = nil;
264269
_shell.reset();
270+
_profiler.reset();
265271
_threadHost.Reset();
266272
_platformViewsController.reset();
267273
}
@@ -319,6 +325,14 @@ - (void)resetChannels {
319325
_settingsChannel.reset();
320326
}
321327

328+
- (void)startProfiler {
329+
_profiler_metrics = std::make_unique<flutter::ProfilerMetricsIOS>();
330+
_profiler = std::make_unique<flutter::SamplingProfiler>(
331+
_threadHost.profiler_thread->GetTaskRunner(),
332+
[self]() { return self->_profiler_metrics->GenerateSample(); }, kNumProfilerSamplesPerSec);
333+
_profiler->Start();
334+
}
335+
322336
// If you add a channel, be sure to also update `resetChannels`.
323337
// Channels get a reference to the engine, and therefore need manual
324338
// cleanup for proper collection.
@@ -438,9 +452,18 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
438452
// initialized.
439453
fml::MessageLoop::EnsureInitializedForCurrentThread();
440454

455+
uint32_t threadHostType = flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU |
456+
flutter::ThreadHost::Type::IO;
457+
bool profilerEnabled = false;
458+
#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) || \
459+
(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE)
460+
profilerEnabled = true;
461+
#endif
462+
if (profilerEnabled) {
463+
threadHostType = threadHostType | flutter::ThreadHost::Type::Profiler;
464+
}
441465
_threadHost = {threadLabel.UTF8String, // label
442-
flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU |
443-
flutter::ThreadHost::Type::IO};
466+
threadHostType};
444467

445468
// Lambda captures by pointers to ObjC objects are fine here because the
446469
// create call is
@@ -456,6 +479,10 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
456479
return std::make_unique<flutter::Rasterizer>(shell, shell.GetTaskRunners());
457480
};
458481

482+
if (profilerEnabled) {
483+
[self startProfiler];
484+
}
485+
459486
if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
460487
// Embedded views requires the gpu and the platform views to be the same.
461488
// The plan is to eventually dynamically merge the threads when there's a
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_
6+
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_
7+
8+
#include <mach/mach.h>
9+
10+
#include <cassert>
11+
#include <optional>
12+
13+
#include "flutter/fml/logging.h"
14+
#include "flutter/shell/profiling/sampling_profiler.h"
15+
16+
namespace flutter {
17+
18+
/**
19+
* @brief Utility class that gathers profiling metrics used by
20+
* `flutter::SamplingProfiler`.
21+
*
22+
* @see flutter::SamplingProfiler
23+
*/
24+
class ProfilerMetricsIOS {
25+
public:
26+
ProfilerMetricsIOS() = default;
27+
28+
ProfileSample GenerateSample();
29+
30+
private:
31+
std::optional<CpuUsageInfo> CpuUsage();
32+
33+
FML_DISALLOW_COPY_AND_ASSIGN(ProfilerMetricsIOS);
34+
};
35+
36+
} // namespace flutter
37+
38+
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
#include "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
6+
7+
namespace {
8+
9+
// RAII holder for `thread_array_t` this is so any early returns in
10+
// `ProfilerMetricsIOS::CpuUsage` don't leak them.
11+
class MachThreads {
12+
public:
13+
thread_array_t threads = NULL;
14+
mach_msg_type_number_t thread_count = 0;
15+
16+
MachThreads() = default;
17+
18+
~MachThreads() {
19+
kern_return_t kernel_return_code = vm_deallocate(
20+
mach_task_self(), reinterpret_cast<vm_offset_t>(threads), thread_count * sizeof(thread_t));
21+
FML_CHECK(kernel_return_code == KERN_SUCCESS) << "Failed to deallocate thread infos.";
22+
}
23+
24+
private:
25+
FML_DISALLOW_COPY_AND_ASSIGN(MachThreads);
26+
};
27+
28+
}
29+
30+
namespace flutter {
31+
32+
ProfileSample ProfilerMetricsIOS::GenerateSample() {
33+
return {.cpu_usage = CpuUsage()};
34+
}
35+
36+
std::optional<CpuUsageInfo> ProfilerMetricsIOS::CpuUsage() {
37+
kern_return_t kernel_return_code;
38+
MachThreads mach_threads = MachThreads();
39+
40+
// Get threads in the task
41+
kernel_return_code =
42+
task_threads(mach_task_self(), &mach_threads.threads, &mach_threads.thread_count);
43+
if (kernel_return_code != KERN_SUCCESS) {
44+
FML_LOG(ERROR) << "Error retrieving task information: "
45+
<< mach_error_string(kernel_return_code);
46+
return std::nullopt;
47+
}
48+
49+
double total_cpu_usage = 0.0;
50+
51+
// Add the CPU usage for each thread. It should be noted that there may be some CPU usage missing
52+
// from this calculation. If a thread ends between calls to this routine, then its info will be
53+
// lost. We could solve this by installing a callback using pthread_key_create. The callback would
54+
// report the thread is ending and allow the code to get the CPU usage. But we need to call
55+
// pthread_setspecific in each thread to set the key's value to a non-null value for the callback
56+
// to work. If we really need this information and if we have a good mechanism for calling
57+
// pthread_setspecific in every thread, then we can include that value in the CPU usage.
58+
for (mach_msg_type_number_t i = 0; i < mach_threads.thread_count; i++) {
59+
thread_basic_info_data_t basic_thread_info;
60+
mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
61+
kernel_return_code =
62+
thread_info(mach_threads.threads[i], THREAD_BASIC_INFO,
63+
reinterpret_cast<thread_info_t>(&basic_thread_info), &thread_info_count);
64+
if (kernel_return_code != KERN_SUCCESS) {
65+
FML_LOG(ERROR) << "Error retrieving thread information: "
66+
<< mach_error_string(kernel_return_code);
67+
return std::nullopt;
68+
}
69+
const double current_thread_cpu_usage =
70+
basic_thread_info.cpu_usage / static_cast<float>(TH_USAGE_SCALE);
71+
total_cpu_usage += current_thread_cpu_usage;
72+
}
73+
74+
flutter::CpuUsageInfo cpu_usage_info = {.num_threads = mach_threads.thread_count,
75+
.total_cpu_usage = total_cpu_usage * 100.0};
76+
return cpu_usage_info;
77+
}
78+
79+
} // namespace flutter

shell/profiling/BUILD.gn

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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/config.gni")
6+
7+
_profiler_deps = [
8+
"//flutter/common",
9+
"//flutter/fml",
10+
]
11+
12+
source_set("profiling") {
13+
sources = [
14+
"sampling_profiler.cc",
15+
"sampling_profiler.h",
16+
]
17+
18+
deps = _profiler_deps
19+
}

0 commit comments

Comments
 (0)