[profiling] CPU Profiling support for iOS (flutter/engine#18087)

See flutter.dev/go/engine-cpu-profiling for details
This commit is contained in:
Kaushik Iska 2020-05-07 08:11:07 -07:00 committed by GitHub
parent 231942e6f6
commit b1b1cea351
13 changed files with 391 additions and 2 deletions

View File

@ -905,6 +905,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_messa
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_router.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/module.modulemap
@ -1223,6 +1225,8 @@ FILE: ../../../flutter/shell/platform/windows/win32_window.cc
FILE: ../../../flutter/shell/platform/windows/win32_window.h
FILE: ../../../flutter/shell/platform/windows/win32_window_unittests.cc
FILE: ../../../flutter/shell/platform/windows/window_state.h
FILE: ../../../flutter/shell/profiling/sampling_profiler.cc
FILE: ../../../flutter/shell/profiling/sampling_profiler.h
FILE: ../../../flutter/shell/version/version.cc
FILE: ../../../flutter/shell/version/version.h
FILE: ../../../flutter/sky/packages/flutter_services/lib/empty.dart

View File

@ -219,6 +219,40 @@ void TraceEventInstant0(TraceArg category_group, TraceArg name) {
);
}
void TraceEventInstant1(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val) {
const char* arg_names[] = {arg1_name};
const char* arg_values[] = {arg1_val};
FlutterTimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_Instant, // event type
1, // argument_count
arg_names, // argument_names
arg_values // argument_values
);
}
void TraceEventInstant2(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val,
TraceArg arg2_name,
TraceArg arg2_val) {
const char* arg_names[] = {arg1_name, arg2_name};
const char* arg_values[] = {arg1_val, arg2_val};
FlutterTimelineEvent(name, // label
Dart_TimelineGetMicros(), // timestamp0
0, // timestamp1_or_async_id
Dart_Timeline_Event_Instant, // event type
2, // argument_count
arg_names, // argument_names
arg_values // argument_values
);
}
void TraceEventFlowBegin0(TraceArg category_group,
TraceArg name,
TraceIDArg id) {
@ -322,6 +356,18 @@ void TraceEventAsyncEnd1(TraceArg category_group,
void TraceEventInstant0(TraceArg category_group, TraceArg name) {}
void TraceEventInstant1(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val) {}
void TraceEventInstant2(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val,
TraceArg arg2_name,
TraceArg arg2_val) {}
void TraceEventFlowBegin0(TraceArg category_group,
TraceArg name,
TraceIDArg id) {}

View File

@ -28,6 +28,10 @@
#define TRACE_EVENT_ASYNC_BEGIN1(a, b, c, d, e) TRACE_ASYNC_BEGIN(a, b, c, d, e)
#define TRACE_EVENT_ASYNC_END1(a, b, c, d, e) TRACE_ASYNC_END(a, b, c, d, e)
#define TRACE_EVENT_INSTANT0(a, b) TRACE_INSTANT(a, b, TRACE_SCOPE_THREAD)
#define TRACE_EVENT_INSTANT1(a, b, k1, v1) \
TRACE_INSTANT(a, b, TRACE_SCOPE_THREAD, k1, v1)
#define TRACE_EVENT_INSTANT2(a, b, k1, v1, k2, v2) \
TRACE_INSTANT(a, b, TRACE_SCOPE_THREAD, k1, v1, k2, v2)
#endif // defined(OS_FUCHSIA)
@ -94,6 +98,14 @@
#define TRACE_EVENT_INSTANT0(category_group, name) \
::fml::tracing::TraceEventInstant0(category_group, name);
#define TRACE_EVENT_INSTANT1(category_group, name, arg1_name, arg1_val) \
::fml::tracing::TraceEventInstant1(category_group, name, arg1_name, arg1_val);
#define TRACE_EVENT_INSTANT2(category_group, name, arg1_name, arg1_val, \
arg2_name, arg2_val) \
::fml::tracing::TraceEventInstant2(category_group, name, arg1_name, \
arg1_val, arg2_name, arg2_val);
#define TRACE_FLOW_BEGIN(category, name, id) \
::fml::tracing::TraceEventFlowBegin0(category, name, id);
@ -272,6 +284,18 @@ void TraceEventAsyncEnd1(TraceArg category_group,
void TraceEventInstant0(TraceArg category_group, TraceArg name);
void TraceEventInstant1(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val);
void TraceEventInstant2(TraceArg category_group,
TraceArg name,
TraceArg arg1_name,
TraceArg arg1_val,
TraceArg arg2_name,
TraceArg arg2_val);
void TraceEventFlowBegin0(TraceArg category_group,
TraceArg name,
TraceIDArg id);

View File

@ -126,6 +126,7 @@ source_set("common") {
"//flutter/fml",
"//flutter/lib/ui",
"//flutter/runtime",
"//flutter/shell/profiling",
"//third_party/dart/runtime:dart_api",
"//third_party/skia",
]

View File

@ -26,6 +26,10 @@ ThreadHost::ThreadHost(std::string name_prefix, uint64_t mask) {
if (mask & ThreadHost::Type::IO) {
io_thread = std::make_unique<fml::Thread>(name_prefix + ".io");
}
if (mask & ThreadHost::Type::Profiler) {
profiler_thread = std::make_unique<fml::Thread>(name_prefix + ".profiler");
}
}
ThreadHost::~ThreadHost() = default;
@ -35,6 +39,7 @@ void ThreadHost::Reset() {
ui_thread.reset();
raster_thread.reset();
io_thread.reset();
profiler_thread.reset();
}
} // namespace flutter

View File

@ -19,12 +19,14 @@ struct ThreadHost {
UI = 1 << 1,
GPU = 1 << 2,
IO = 1 << 3,
Profiler = 1 << 4,
};
std::unique_ptr<fml::Thread> platform_thread;
std::unique_ptr<fml::Thread> ui_thread;
std::unique_ptr<fml::Thread> raster_thread;
std::unique_ptr<fml::Thread> io_thread;
std::unique_ptr<fml::Thread> profiler_thread;
ThreadHost();

View File

@ -79,6 +79,8 @@ source_set("flutter_framework_source") {
"framework/Source/platform_message_response_darwin.mm",
"framework/Source/platform_message_router.h",
"framework/Source/platform_message_router.mm",
"framework/Source/profiler_metrics_ios.h",
"framework/Source/profiler_metrics_ios.mm",
"framework/Source/vsync_waiter_ios.h",
"framework/Source/vsync_waiter_ios.mm",
"ios_context.h",
@ -131,6 +133,7 @@ source_set("flutter_framework_source") {
"//flutter/shell/common",
"//flutter/shell/platform/darwin/common",
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/profiling:profiling",
"//third_party/skia",
]

View File

@ -24,11 +24,14 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
#import "flutter/shell/platform/darwin/ios/ios_surface.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#include "flutter/shell/platform/darwin/ios/rendering_api_selection.h"
#include "flutter/shell/profiling/sampling_profiler.h"
NSString* const FlutterDefaultDartEntrypoint = nil;
static constexpr int kNumProfilerSamplesPerSec = 5;
@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
@property(nonatomic, assign) FlutterEngine* flutterEngine;
@ -56,6 +59,8 @@ NSString* const FlutterDefaultDartEntrypoint = nil;
fml::scoped_nsobject<FlutterObservatoryPublisher> _publisher;
std::unique_ptr<flutter::FlutterPlatformViewsController> _platformViewsController;
std::unique_ptr<flutter::ProfilerMetricsIOS> _profiler_metrics;
std::unique_ptr<flutter::SamplingProfiler> _profiler;
// Channels
fml::scoped_nsobject<FlutterPlatformPlugin> _platformPlugin;
@ -262,6 +267,7 @@ NSString* const FlutterDefaultDartEntrypoint = nil;
[self resetChannels];
self.isolateId = nil;
_shell.reset();
_profiler.reset();
_threadHost.Reset();
_platformViewsController.reset();
}
@ -319,6 +325,14 @@ NSString* const FlutterDefaultDartEntrypoint = nil;
_settingsChannel.reset();
}
- (void)startProfiler {
_profiler_metrics = std::make_unique<flutter::ProfilerMetricsIOS>();
_profiler = std::make_unique<flutter::SamplingProfiler>(
_threadHost.profiler_thread->GetTaskRunner(),
[self]() { return self->_profiler_metrics->GenerateSample(); }, kNumProfilerSamplesPerSec);
_profiler->Start();
}
// If you add a channel, be sure to also update `resetChannels`.
// Channels get a reference to the engine, and therefore need manual
// cleanup for proper collection.
@ -438,9 +452,18 @@ NSString* const FlutterDefaultDartEntrypoint = nil;
// initialized.
fml::MessageLoop::EnsureInitializedForCurrentThread();
uint32_t threadHostType = flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU |
flutter::ThreadHost::Type::IO;
bool profilerEnabled = false;
#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) || \
(FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_PROFILE)
profilerEnabled = true;
#endif
if (profilerEnabled) {
threadHostType = threadHostType | flutter::ThreadHost::Type::Profiler;
}
_threadHost = {threadLabel.UTF8String, // label
flutter::ThreadHost::Type::UI | flutter::ThreadHost::Type::GPU |
flutter::ThreadHost::Type::IO};
threadHostType};
// Lambda captures by pointers to ObjC objects are fine here because the
// create call is
@ -456,6 +479,10 @@ NSString* const FlutterDefaultDartEntrypoint = nil;
return std::make_unique<flutter::Rasterizer>(shell, shell.GetTaskRunners());
};
if (profilerEnabled) {
[self startProfiler];
}
if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
// Embedded views requires the gpu and the platform views to be the same.
// The plan is to eventually dynamically merge the threads when there's a

View File

@ -0,0 +1,38 @@
// 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.
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_
#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_
#include <mach/mach.h>
#include <cassert>
#include <optional>
#include "flutter/fml/logging.h"
#include "flutter/shell/profiling/sampling_profiler.h"
namespace flutter {
/**
* @brief Utility class that gathers profiling metrics used by
* `flutter::SamplingProfiler`.
*
* @see flutter::SamplingProfiler
*/
class ProfilerMetricsIOS {
public:
ProfilerMetricsIOS() = default;
ProfileSample GenerateSample();
private:
std::optional<CpuUsageInfo> CpuUsage();
FML_DISALLOW_COPY_AND_ASSIGN(ProfilerMetricsIOS);
};
} // namespace flutter
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_

View File

@ -0,0 +1,79 @@
// 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.
#include "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h"
namespace {
// RAII holder for `thread_array_t` this is so any early returns in
// `ProfilerMetricsIOS::CpuUsage` don't leak them.
class MachThreads {
public:
thread_array_t threads = NULL;
mach_msg_type_number_t thread_count = 0;
MachThreads() = default;
~MachThreads() {
kern_return_t kernel_return_code = vm_deallocate(
mach_task_self(), reinterpret_cast<vm_offset_t>(threads), thread_count * sizeof(thread_t));
FML_CHECK(kernel_return_code == KERN_SUCCESS) << "Failed to deallocate thread infos.";
}
private:
FML_DISALLOW_COPY_AND_ASSIGN(MachThreads);
};
}
namespace flutter {
ProfileSample ProfilerMetricsIOS::GenerateSample() {
return {.cpu_usage = CpuUsage()};
}
std::optional<CpuUsageInfo> ProfilerMetricsIOS::CpuUsage() {
kern_return_t kernel_return_code;
MachThreads mach_threads = MachThreads();
// Get threads in the task
kernel_return_code =
task_threads(mach_task_self(), &mach_threads.threads, &mach_threads.thread_count);
if (kernel_return_code != KERN_SUCCESS) {
FML_LOG(ERROR) << "Error retrieving task information: "
<< mach_error_string(kernel_return_code);
return std::nullopt;
}
double total_cpu_usage = 0.0;
// Add the CPU usage for each thread. It should be noted that there may be some CPU usage missing
// from this calculation. If a thread ends between calls to this routine, then its info will be
// lost. We could solve this by installing a callback using pthread_key_create. The callback would
// report the thread is ending and allow the code to get the CPU usage. But we need to call
// pthread_setspecific in each thread to set the key's value to a non-null value for the callback
// to work. If we really need this information and if we have a good mechanism for calling
// pthread_setspecific in every thread, then we can include that value in the CPU usage.
for (mach_msg_type_number_t i = 0; i < mach_threads.thread_count; i++) {
thread_basic_info_data_t basic_thread_info;
mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
kernel_return_code =
thread_info(mach_threads.threads[i], THREAD_BASIC_INFO,
reinterpret_cast<thread_info_t>(&basic_thread_info), &thread_info_count);
if (kernel_return_code != KERN_SUCCESS) {
FML_LOG(ERROR) << "Error retrieving thread information: "
<< mach_error_string(kernel_return_code);
return std::nullopt;
}
const double current_thread_cpu_usage =
basic_thread_info.cpu_usage / static_cast<float>(TH_USAGE_SCALE);
total_cpu_usage += current_thread_cpu_usage;
}
flutter::CpuUsageInfo cpu_usage_info = {.num_threads = mach_threads.thread_count,
.total_cpu_usage = total_cpu_usage * 100.0};
return cpu_usage_info;
}
} // namespace flutter

View File

@ -0,0 +1,19 @@
# 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.
import("//flutter/shell/config.gni")
_profiler_deps = [
"//flutter/common",
"//flutter/fml",
]
source_set("profiling") {
sources = [
"sampling_profiler.cc",
"sampling_profiler.h",
]
deps = _profiler_deps
}

View File

@ -0,0 +1,49 @@
// 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.
#include "flutter/shell/profiling/sampling_profiler.h"
namespace flutter {
SamplingProfiler::SamplingProfiler(
fml::RefPtr<fml::TaskRunner> profiler_task_runner,
Sampler sampler,
int num_samples_per_sec)
: profiler_task_runner_(profiler_task_runner),
sampler_(std::move(sampler)),
num_samples_per_sec_(num_samples_per_sec) {}
void SamplingProfiler::Start() const {
if (!profiler_task_runner_) {
return;
}
FML_CHECK(num_samples_per_sec_ > 0)
<< "number of samples must be a positive integer, got: "
<< num_samples_per_sec_;
double delay_between_samples = 1.0 / num_samples_per_sec_;
auto task_delay = fml::TimeDelta::FromSecondsF(delay_between_samples);
SampleRepeatedly(task_delay);
}
void SamplingProfiler::SampleRepeatedly(fml::TimeDelta task_delay) const {
profiler_task_runner_->PostDelayedTask(
[profiler = this, task_delay = task_delay, sampler = sampler_]() {
const ProfileSample usage = sampler();
if (usage.cpu_usage) {
const auto& cpu_usage = usage.cpu_usage;
// TODO(kaushikiska): consider buffering these every n seconds to
// avoid spamming the trace buffer.
std::string total_cpu_usage =
std::to_string(cpu_usage->total_cpu_usage);
std::string num_threads = std::to_string(cpu_usage->num_threads);
TRACE_EVENT_INSTANT2("flutter::profiling", "CpuUsage",
"total_cpu_usage", total_cpu_usage.c_str(),
"num_threads", num_threads.c_str());
}
profiler->SampleRepeatedly(task_delay);
},
task_delay);
}
} // namespace flutter

View File

@ -0,0 +1,92 @@
// 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.
#ifndef FLUTTER_SHELL_PROFILING_SAMPLING_PROFILER_H_
#define FLUTTER_SHELL_PROFILING_SAMPLING_PROFILER_H_
#include <functional>
#include <memory>
#include <optional>
#include "flutter/fml/task_runner.h"
#include "flutter/fml/trace_event.h"
namespace flutter {
/**
* @brief CPU usage stats. `num_threads` is the number of threads owned by the
* process. It is to be noted that this is not per shell, there can be multiple
* shells within the process. `total_cpu_usage` is the percentage (between [0,
* 100]) cpu usage of the application. This is across all the cores, for example
* an application using 100% of all the core will report `total_cpu_usage` as
* `100`, if it has 100% across 2 cores and 0% across the other cores, embedder
* must report `total_cpu_usage` as `50`.
*/
struct CpuUsageInfo {
uint32_t num_threads;
double total_cpu_usage;
};
/**
* @brief Container for the metrics we collect during each run of `Sampler`.
* This currently holds `CpuUsageInfo` but the intent is to expand it to other
* metrics.
*
* @see flutter::Sampler
*/
struct ProfileSample {
std::optional<CpuUsageInfo> cpu_usage;
};
/**
* @brief Sampler is run during `SamplingProfiler::SampleRepeatedly`. Each
* platform should implement its version of a `Sampler` if they decide to
* participate in gathering profiling metrics.
*
* @see flutter::SamplingProfiler::SampleRepeatedly
*/
using Sampler = std::function<ProfileSample(void)>;
/**
* @brief a Sampling Profiler that runs peridically and calls the `Sampler`
* which servers as a value function to gather various profiling metrics as
* represented by `ProfileSample`. These profiling metrics are then posted to
* the observatory timeline.
*
*/
class SamplingProfiler {
public:
/**
* @brief Construct a new Sampling Profiler object
*
* @param profiler_task_runner the task runner to service sampling requests.
* @param sampler the value function to collect the profiling metrics.
* @param num_samples_per_sec number of times you wish to run the sampler per
* second.
*
* @see fml::TaskRunner
*/
SamplingProfiler(fml::RefPtr<fml::TaskRunner> profiler_task_runner,
Sampler sampler,
int num_samples_per_sec);
/**
* @brief Starts the SamplingProfiler by triggering `SampleRepeatedly`.
*
*/
void Start() const;
private:
const fml::RefPtr<fml::TaskRunner> profiler_task_runner_;
const Sampler sampler_;
const uint32_t num_samples_per_sec_;
void SampleRepeatedly(fml::TimeDelta task_delay) const;
FML_DISALLOW_COPY_AND_ASSIGN(SamplingProfiler);
};
} // namespace flutter
#endif // FLUTTER_SHELL_PROFILING_SAMPLING_PROFILER_H_