Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions fml/trace_event.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {}
Expand Down
24 changes: 24 additions & 0 deletions fml/trace_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TRACE_EVENT_INSTANT1 seems to be unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's nice to have 0, 1 and 2 rather than just 0 and 2. If you feel strongly about this, I can remove it.

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)

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions shell/common/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down
5 changes: 5 additions & 0 deletions shell/common/thread_host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,6 +39,7 @@ void ThreadHost::Reset() {
ui_thread.reset();
raster_thread.reset();
io_thread.reset();
profiler_thread.reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Profiling applies to all shells in the process. Ideally, there would only be one profiler in the process at any given time with the shells having to ability to turn this on or off. How about putting this thread by the concurrent message loop threads in DartVM (which is also process global) or just using the one of the threads in the concurrent thread pool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chinmaygarde I agree that there only needs to be one profiler for all the shells. Moving this to the concurrent thread loop is a more involved change, given that the concurrent thread loops don't have scheduling mechanisms (at fixed delays).

Mind if I file an issue for this and address it in a follow up change. I think this will be useful as-is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

}

} // namespace flutter
2 changes: 2 additions & 0 deletions shell/common/thread_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
3 changes: 3 additions & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
]

Expand Down
31 changes: 29 additions & 2 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,6 +59,8 @@ @implementation FlutterEngine {
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;
Expand Down Expand Up @@ -262,6 +267,7 @@ - (void)destroyContext {
[self resetChannels];
self.isolateId = nil;
_shell.reset();
_profiler.reset();
_threadHost.Reset();
_platformViewsController.reset();
}
Expand Down Expand Up @@ -319,6 +325,14 @@ - (void)resetChannels {
_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.
Expand Down Expand Up @@ -438,9 +452,18 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
// 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
Expand All @@ -456,6 +479,10 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI {
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
Expand Down
38 changes: 38 additions & 0 deletions shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h
Original file line number Diff line number Diff line change
@@ -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_
79 changes: 79 additions & 0 deletions shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that we can also do task_threads(mach_task_self(), &mach_threads.threads, &mach_threads.thread_count); inside the constructor of this class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would make failing early by returning std::nullopt rather ugly. I left it as is for now.

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
19 changes: 19 additions & 0 deletions shell/profiling/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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
}
Loading