From a1e31e135635d5d0c23f8022de320d71d6ae09b7 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Fri, 1 May 2020 07:51:17 -0700 Subject: [PATCH] [profiling] CPU Profiling support for iOS See flutter.dev/go/engine-cpu-profiling for details --- ci/licenses_golden/licenses_flutter | 4 + fml/trace_event.cc | 46 ++++++++++ fml/trace_event.h | 24 +++++ shell/common/BUILD.gn | 1 + shell/common/thread_host.cc | 5 + shell/common/thread_host.h | 2 + shell/platform/darwin/ios/BUILD.gn | 3 + .../ios/framework/Source/FlutterEngine.mm | 31 ++++++- .../framework/Source/profiler_metrics_ios.h | 38 ++++++++ .../framework/Source/profiler_metrics_ios.mm | 79 ++++++++++++++++ shell/profiling/BUILD.gn | 19 ++++ shell/profiling/sampling_profiler.cc | 49 ++++++++++ shell/profiling/sampling_profiler.h | 92 +++++++++++++++++++ 13 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h create mode 100644 shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm create mode 100644 shell/profiling/BUILD.gn create mode 100644 shell/profiling/sampling_profiler.cc create mode 100644 shell/profiling/sampling_profiler.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7da60486822fe..35bed4bebab5f 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -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 diff --git a/fml/trace_event.cc b/fml/trace_event.cc index d055582d3a0eb..56a8838647d5d 100644 --- a/fml/trace_event.cc +++ b/fml/trace_event.cc @@ -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) {} diff --git a/fml/trace_event.h b/fml/trace_event.h index 98d328e134819..225fa9e2771c7 100644 --- a/fml/trace_event.h +++ b/fml/trace_event.h @@ -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); diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index d84acd2db8aef..e9e5965dffda0 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -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", ] diff --git a/shell/common/thread_host.cc b/shell/common/thread_host.cc index 5e2201e097a86..7f76bbf0a846e 100644 --- a/shell/common/thread_host.cc +++ b/shell/common/thread_host.cc @@ -26,6 +26,10 @@ ThreadHost::ThreadHost(std::string name_prefix, uint64_t mask) { if (mask & ThreadHost::Type::IO) { io_thread = std::make_unique(name_prefix + ".io"); } + + if (mask & ThreadHost::Type::Profiler) { + profiler_thread = std::make_unique(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 diff --git a/shell/common/thread_host.h b/shell/common/thread_host.h index 9f2643ed7e4f0..679168ddaca53 100644 --- a/shell/common/thread_host.h +++ b/shell/common/thread_host.h @@ -19,12 +19,14 @@ struct ThreadHost { UI = 1 << 1, GPU = 1 << 2, IO = 1 << 3, + Profiler = 1 << 4, }; std::unique_ptr platform_thread; std::unique_ptr ui_thread; std::unique_ptr raster_thread; std::unique_ptr io_thread; + std::unique_ptr profiler_thread; ThreadHost(); diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 7f5870a043128..f2eead712db4c 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -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", ] diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 35cf9553b0c61..c7caf41f85306 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -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 @property(nonatomic, assign) FlutterEngine* flutterEngine; @@ -56,6 +59,8 @@ @implementation FlutterEngine { fml::scoped_nsobject _publisher; std::unique_ptr _platformViewsController; + std::unique_ptr _profiler_metrics; + std::unique_ptr _profiler; // Channels fml::scoped_nsobject _platformPlugin; @@ -262,6 +267,7 @@ - (void)destroyContext { [self resetChannels]; self.isolateId = nil; _shell.reset(); + _profiler.reset(); _threadHost.Reset(); _platformViewsController.reset(); } @@ -319,6 +325,14 @@ - (void)resetChannels { _settingsChannel.reset(); } +- (void)startProfiler { + _profiler_metrics = std::make_unique(); + _profiler = std::make_unique( + _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 @@ - (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 @@ -456,6 +479,10 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { return std::make_unique(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 diff --git a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h new file mode 100644 index 0000000000000..d8c36cd290512 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h @@ -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 + +#include +#include + +#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 CpuUsage(); + + FML_DISALLOW_COPY_AND_ASSIGN(ProfilerMetricsIOS); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_PROFILER_METRICS_IOS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm new file mode 100644 index 0000000000000..54a15945a3e45 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.mm @@ -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(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 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(&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(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 diff --git a/shell/profiling/BUILD.gn b/shell/profiling/BUILD.gn new file mode 100644 index 0000000000000..0ddef5cde23f1 --- /dev/null +++ b/shell/profiling/BUILD.gn @@ -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 +} diff --git a/shell/profiling/sampling_profiler.cc b/shell/profiling/sampling_profiler.cc new file mode 100644 index 0000000000000..6bdd435dda647 --- /dev/null +++ b/shell/profiling/sampling_profiler.cc @@ -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 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 diff --git a/shell/profiling/sampling_profiler.h b/shell/profiling/sampling_profiler.h new file mode 100644 index 0000000000000..be8465df30229 --- /dev/null +++ b/shell/profiling/sampling_profiler.h @@ -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 +#include +#include + +#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 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; + +/** + * @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 profiler_task_runner, + Sampler sampler, + int num_samples_per_sec); + + /** + * @brief Starts the SamplingProfiler by triggering `SampleRepeatedly`. + * + */ + void Start() const; + + private: + const fml::RefPtr 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_