From d174ec4afd52b92f7671753af6114e3804382065 Mon Sep 17 00:00:00 2001 From: Bo-Rong Chen Date: Sat, 27 Jan 2024 17:54:45 -0800 Subject: [PATCH] [android] Measuring the performance of SbPlayer This CL includes the performance metrics as the following: 1. CPU usage: the percentage of time spent executing across all threads of the process. It could exceed 100% on multi-core systems. 2. Dropped frames: the number of dropped video frames. 3. Audio underrun count: the number of audio buffer to underflow and a potential audio glitch or pop. b/315159208 --- cobalt/base/statistics.h | 45 ++- cobalt/media/BUILD.gn | 2 + cobalt/media/base/sbplayer_bridge.cc | 45 +++ cobalt/media/base/sbplayer_bridge.h | 4 + cobalt/media/base/sbplayer_perf.cc | 359 +++++++++++++++++ cobalt/media/base/sbplayer_perf.h | 74 ++++ cobalt/media/player/web_media_player_impl.cc | 12 + cobalt/network/network_module.cc | 12 + cobalt/web/agent.cc | 12 + starboard/android/shared/BUILD.gn | 2 + .../shared/audio_track_audio_sink_type.cc | 6 + starboard/android/shared/media_decoder.cc | 7 + .../shared/player_components_factory.h | 3 + starboard/android/shared/player_perf.cc | 362 ++++++++++++++++++ starboard/android/shared/player_perf.h | 35 ++ .../android/shared/system_get_extensions.cc | 5 + starboard/extension/extension_test.cc | 29 ++ starboard/extension/player_perf.h | 60 +++ 18 files changed, 1069 insertions(+), 5 deletions(-) create mode 100644 cobalt/media/base/sbplayer_perf.cc create mode 100644 cobalt/media/base/sbplayer_perf.h create mode 100644 starboard/android/shared/player_perf.cc create mode 100644 starboard/android/shared/player_perf.h create mode 100644 starboard/extension/player_perf.h diff --git a/cobalt/base/statistics.h b/cobalt/base/statistics.h index 13c489115d42..d89f837715a7 100644 --- a/cobalt/base/statistics.h +++ b/cobalt/base/statistics.h @@ -37,6 +37,12 @@ inline int64_t DefaultSampleToValueFunc(int64_t dividend, int64_t divisor) { } // namespace internal +typedef struct Percentiles { + int64_t percentile_25th; + int64_t median; + int64_t percentile_75th; +} Percentiles; + // Track the statistics of a series of generic samples reprensented as // dividends and divisors in integer types. The value of a sample is calculated // by `SampleToValueFunc` using its dividend and divisor. @@ -78,6 +84,8 @@ class Statistics { int64_t max() const { return 0; } int64_t GetMedian() const { return 0; } + void GetPercentiles(Percentiles* out_percentiles) {} + size_t GetNumberOfSamples() { return 0; } }; #else // defined(COBALT_BUILD_TYPE_GOLD) @@ -151,14 +159,33 @@ class Statistics { samples_to_copy -= MaxSamples - first_sample_index_; copy.insert(copy.end(), samples_, samples_ + samples_to_copy); } + return GetNthPercentile(copy, number_of_samples_ / 2); + } - std::nth_element(copy.begin(), copy.begin() + number_of_samples_ / 2, - copy.end(), [](const Sample& left, const Sample& right) { - return GetSampleValue(left) < GetSampleValue(right); - }); - return GetSampleValue(copy[number_of_samples_ / 2]); + void GetPercentiles(Percentiles* out_percentiles) { + if (number_of_samples_ == 0) { + return; + } + std::vector copy; + copy.reserve(number_of_samples_); + if (first_sample_index_ + number_of_samples_ <= MaxSamples) { + copy.assign(samples_ + first_sample_index_, + samples_ + first_sample_index_ + number_of_samples_); + } else { + auto samples_to_copy = number_of_samples_; + copy.assign(samples_ + first_sample_index_, samples_ + MaxSamples); + samples_to_copy -= MaxSamples - first_sample_index_; + copy.insert(copy.end(), samples_, samples_ + samples_to_copy); + } + out_percentiles->percentile_25th = + GetNthPercentile(copy, number_of_samples_ / 4); + out_percentiles->median = GetNthPercentile(copy, number_of_samples_ / 2); + out_percentiles->percentile_75th = + GetNthPercentile(copy, number_of_samples_ * 3 / 4); } + size_t GetNumberOfSamples() { return number_of_samples_; } + private: Statistics(const Statistics&) = delete; Statistics& operator=(const Statistics&) = delete; @@ -173,6 +200,14 @@ class Statistics { static_cast(sample.divisor)); } + static int GetNthPercentile(std::vector& copy, int place) { + std::nth_element(copy.begin(), copy.begin() + place, copy.end(), + [](const Sample& left, const Sample& right) { + return GetSampleValue(left) < GetSampleValue(right); + }); + return GetSampleValue(copy[place]); + } + bool first_sample_added_ = false; int64_t accumulated_dividend_ = 0; diff --git a/cobalt/media/BUILD.gn b/cobalt/media/BUILD.gn index a362d9a47094..c566deddb7cd 100644 --- a/cobalt/media/BUILD.gn +++ b/cobalt/media/BUILD.gn @@ -51,6 +51,8 @@ component("media") { "base/sbplayer_bridge.h", "base/sbplayer_interface.cc", "base/sbplayer_interface.h", + "base/sbplayer_perf.cc", + "base/sbplayer_perf.h", "base/sbplayer_pipeline.cc", "base/sbplayer_pipeline.h", "base/sbplayer_set_bounds_helper.cc", diff --git a/cobalt/media/base/sbplayer_bridge.cc b/cobalt/media/base/sbplayer_bridge.cc index 0e5847119fdd..0162e8cae3ab 100644 --- a/cobalt/media/base/sbplayer_bridge.cc +++ b/cobalt/media/base/sbplayer_bridge.cc @@ -33,6 +33,7 @@ #include "starboard/common/player.h" #include "starboard/common/string.h" #include "starboard/configuration.h" +#include "starboard/extension/player_perf.h" #include "starboard/extension/player_set_max_video_input_size.h" #include "starboard/memory.h" @@ -46,6 +47,12 @@ using base::TimeDelta; using starboard::FormatString; using starboard::GetPlayerOutputModeName; +#if !defined(COBALT_BUILD_TYPE_GOLD) +constexpr bool kForceCPUUtilizationMeasure = true; +#else +constexpr bool kForceCPUUtilizationMeasure = false; +#endif // !defined(COBALT_BUILD_TYPE_GOLD) + class StatisticsWrapper { public: static StatisticsWrapper* GetInstance(); @@ -253,6 +260,7 @@ SbPlayerBridge::SbPlayerBridge( decode_target_provider_(decode_target_provider), max_video_capabilities_(max_video_capabilities), max_video_input_size_(max_video_input_size), + perf_thread_("sbplayer_perf"), cval_stats_(&interface->cval_stats_), pipeline_identifier_(pipeline_identifier) #if SB_HAS(PLAYER_WITH_URL) @@ -277,6 +285,19 @@ SbPlayerBridge::SbPlayerBridge( SendColorSpaceHistogram(); } + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (kForceCPUUtilizationMeasure && player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + perf_thread_.Start(); + player_perf_extension->AddThreadID( + perf_thread_.thread_name().c_str(), + static_cast(perf_thread_.GetThreadId())); + } + output_mode_ = ComputeSbPlayerOutputMode(default_output_mode); CreatePlayer(); @@ -292,6 +313,19 @@ SbPlayerBridge::SbPlayerBridge( SbPlayerBridge::~SbPlayerBridge() { DCHECK(task_runner_->RunsTasksInCurrentSequence()); + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (kForceCPUUtilizationMeasure && player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + if (sbplayer_perf_) { + sbplayer_perf_->Stop(); + } + perf_thread_.Stop(); + } + callback_helper_->ResetPlayer(); set_bounds_helper_->SetPlayerBridge(NULL); @@ -798,6 +832,17 @@ void SbPlayerBridge::CreatePlayer() { &SbPlayerBridge::DecoderStatusCB, &SbPlayerBridge::PlayerStatusCB, &SbPlayerBridge::PlayerErrorCB, this, get_decode_target_graphics_context_provider_func_.Run()); + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (kForceCPUUtilizationMeasure && player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + sbplayer_perf_ = new SbPlayerPerf(sbplayer_interface_, player_, + perf_thread_.task_runner()); + sbplayer_perf_->Start(); + } cval_stats_->StopTimer(MediaTiming::SbPlayerCreate, pipeline_identifier_); is_creating_player_ = false; diff --git a/cobalt/media/base/sbplayer_bridge.h b/cobalt/media/base/sbplayer_bridge.h index f6cc6879c12b..0d37d9d1ad22 100644 --- a/cobalt/media/base/sbplayer_bridge.h +++ b/cobalt/media/base/sbplayer_bridge.h @@ -29,6 +29,7 @@ #include "cobalt/media/base/decode_target_provider.h" #include "cobalt/media/base/decoder_buffer_cache.h" #include "cobalt/media/base/sbplayer_interface.h" +#include "cobalt/media/base/sbplayer_perf.h" #include "cobalt/media/base/sbplayer_set_bounds_helper.h" #include "media/base/audio_decoder_config.h" #include "media/base/decoder_buffer.h" @@ -327,6 +328,9 @@ class SbPlayerBridge { bool pending_audio_eos_buffer_ = false; bool pending_video_eos_buffer_ = false; + base::Thread perf_thread_; + SbPlayerPerf* sbplayer_perf_; + CValStats* cval_stats_; std::string pipeline_identifier_; }; diff --git a/cobalt/media/base/sbplayer_perf.cc b/cobalt/media/base/sbplayer_perf.cc new file mode 100644 index 000000000000..5d3903a19708 --- /dev/null +++ b/cobalt/media/base/sbplayer_perf.cc @@ -0,0 +1,359 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/media/base/sbplayer_perf.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "cobalt/base/statistics.h" +#include "starboard/common/once.h" +#include "starboard/extension/player_perf.h" +#include "starboard/player.h" +#include "starboard/system.h" + +namespace cobalt { +namespace media { + +namespace { + +// Sampling every 1 second +const base::TimeDelta sampling_interval = + base::Milliseconds(20); // base::Seconds(1); + +// Measuring for 2 minutes +const int64_t kMaxMeasurementSamples = 120; + +class StatisticsWrapper { + public: + static StatisticsWrapper* GetInstance(); + base::Statistics cpu_usage{"SbPlayer.CPUUsage"}; + base::Statistics decoded_speed{"SbPlayer.decoded_speed"}; + base::Statistics audio_track_aud{ + "SbPlayer.audio_track_aud"}; + base::Statistics video_decoder{"SbPlayer.video_decoder"}; + base::Statistics audio_decoder{"SbPlayer.audio_decoder"}; + base::Statistics media_pipeline{"SbPlayer.media_pipeline"}; + base::Statistics player_worker{"SbPlayer.player_worker"}; + base::Statistics main_web_module{ + "SbPlayer.main_web_module"}; + base::Statistics network_module{"SbPlayer.network_module"}; + base::Statistics sbplayer_perf{"SbPlayer.sbplayer_perf"}; +}; + +} // namespace + +SB_ONCE_INITIALIZE_FUNCTION(StatisticsWrapper, StatisticsWrapper::GetInstance); + +void SbPlayerPerf::Start() { + task_runner_->PostTask( + FROM_HERE, base::Bind(&SbPlayerPerf::StartTask, base::Unretained(this))); +} + +void SbPlayerPerf::StartTask() { + DCHECK(task_runner_->BelongsToCurrentThread()); + start_timestamp_ = Time::Now(); + timer_.Start(FROM_HERE, sampling_interval, this, + &SbPlayerPerf::GetPlatformIndependentCPUUsage); +} + +void SbPlayerPerf::Stop() { + task_runner_->PostTask( + FROM_HERE, base::Bind(&SbPlayerPerf::StopTask, base::Unretained(this))); +} + +void SbPlayerPerf::StopTask() { + DCHECK(task_runner_->BelongsToCurrentThread()); + timer_.Stop(); +} + +void SbPlayerPerf::GetPlatformIndependentCPUUsage() { + DCHECK(task_runner_->BelongsToCurrentThread()); + + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + Time timestamp_now = Time::Now(); + base::Percentiles cpu_out_percentiles, decoded_speed_out_percentiles; + base::Percentiles audio_track_aud_out_percentiles, + video_decoder_out_percentiles; + base::Percentiles audio_decoder_out_percentiles, + media_pipeline_out_percentiles; + base::Percentiles player_worker_out_percentiles, + sbplayer_perf_out_percentiles, main_web_module_out_percentiles, + network_module_out_percentiles; + std::unordered_map map_cumulative_cpu = + player_perf_extension->GetPlatformIndependentCPUUsage(); + double cpu_usage_perf = map_cumulative_cpu["system"]; + +#if SB_API_VERSION >= 15 + SbPlayerInfo info; +#else // SB_API_VERSION >= 15 + SbPlayerInfo2 info; +#endif // SB_API_VERSION >= 15 + interface_->GetInfo(player_, &info); + if (cpu_usage_perf == 0.0 && + StatisticsWrapper::GetInstance()->cpu_usage.GetNumberOfSamples() == 0 && + decoded_frames_last_ == 0) { + // The first sample is 0, so skip it + decoded_frames_last_ = info.total_video_frames; + timestamp_last_ = timestamp_now; + return; + } + + std::string result_str = "CPU:" + std::to_string(cpu_usage_perf) + ";"; + StatisticsWrapper::GetInstance()->cpu_usage.AddSample(cpu_usage_perf, 1); + for (auto element : map_cumulative_cpu) { + if (element.second == 0) continue; + if (element.first.compare("audio_track_aud") == 0) { + StatisticsWrapper::GetInstance()->audio_track_aud.AddSample( + element.second, 1); + } else if (element.first.compare("video_decoder") == 0) { + StatisticsWrapper::GetInstance()->video_decoder.AddSample( + element.second, 1); + } else if (element.first.compare("audio_decoder") == 0) { + StatisticsWrapper::GetInstance()->audio_decoder.AddSample( + element.second, 1); + } else if (element.first.compare("media_pipeline") == 0) { + StatisticsWrapper::GetInstance()->media_pipeline.AddSample( + element.second, 1); + } else if (element.first.compare("player_worker") == 0) { + StatisticsWrapper::GetInstance()->player_worker.AddSample( + element.second, 1); + } else if (element.first.compare("sbplayer_perf") == 0) { + StatisticsWrapper::GetInstance()->sbplayer_perf.AddSample( + element.second, 1); + } else if (element.first.compare("MainWebModule") == 0) { + StatisticsWrapper::GetInstance()->main_web_module.AddSample( + element.second, 1); + } else if (element.first.compare("NetworkModule") == 0) { + StatisticsWrapper::GetInstance()->network_module.AddSample( + element.second, 1); + } + result_str += element.first + ":" + std::to_string(element.second) + ";"; + } + + int decoded_frames = info.total_video_frames - decoded_frames_last_; + TimeDelta interval = timestamp_now - timestamp_last_; + if (decoded_frames > 0 && interval.InSeconds() > 0) { + double decoded_speed = static_cast(decoded_frames) / + static_cast(interval.InSeconds()); + StatisticsWrapper::GetInstance()->decoded_speed.AddSample(decoded_speed, + 1); + } + decoded_frames_last_ = info.total_video_frames; + timestamp_last_ = timestamp_now; + + result_str += + "Total_Frames:" + std::to_string(info.total_video_frames) + ";"; + result_str += + "Dropped_Frames:" + std::to_string(info.dropped_video_frames) + ";"; + LOG(ERROR) << "Brown " << result_str.c_str(); + + if (false && + StatisticsWrapper::GetInstance()->cpu_usage.GetNumberOfSamples() % + kMaxMeasurementSamples == + 0) { + StatisticsWrapper::GetInstance()->cpu_usage.GetPercentiles( + &cpu_out_percentiles); + StatisticsWrapper::GetInstance()->decoded_speed.GetPercentiles( + &decoded_speed_out_percentiles); + StatisticsWrapper::GetInstance()->audio_track_aud.GetPercentiles( + &audio_track_aud_out_percentiles); + StatisticsWrapper::GetInstance()->video_decoder.GetPercentiles( + &video_decoder_out_percentiles); + StatisticsWrapper::GetInstance()->audio_decoder.GetPercentiles( + &audio_decoder_out_percentiles); + StatisticsWrapper::GetInstance()->media_pipeline.GetPercentiles( + &media_pipeline_out_percentiles); + StatisticsWrapper::GetInstance()->player_worker.GetPercentiles( + &player_worker_out_percentiles); + StatisticsWrapper::GetInstance()->main_web_module.GetPercentiles( + &main_web_module_out_percentiles); + StatisticsWrapper::GetInstance()->network_module.GetPercentiles( + &network_module_out_percentiles); + StatisticsWrapper::GetInstance()->sbplayer_perf.GetPercentiles( + &sbplayer_perf_out_percentiles); + + double dropped_frame_percentage = + 100.0 * static_cast(info.dropped_video_frames) / + static_cast(info.total_video_frames); + TimeDelta duration = Time::Now() - start_timestamp_; + LOG(ERROR) + << "Brown Brand/model_name:" << sb_system_property_brand_name_ << "_" + << sb_system_property_model_name_ + << ";;platform_name:" << sb_system_property_platform_name_ + << ";;num_of_processors:" << sb_number_of_processors_ + << ";;duration:" << duration.InSeconds() << ";;audio_codec:" + << player_perf_extension->GetCurrentMediaAudioCodecName() + << ";;video_codec:" + << player_perf_extension->GetCurrentMediaVideoCodecName() + << ";;should_be_paused:" + << player_perf_extension->GetCountShouldBePaused() + << ";;dropped_frames(%):" << dropped_frame_percentage + << ",dropped_frames:" << info.dropped_video_frames + << ",total_frames:" << info.total_video_frames + << ";;audio_underrun_count:" + << player_perf_extension->GetAudioUnderrunCount() + << ";;playback_rate:" << info.playback_rate + << ";;use_neon:" << player_perf_extension->IsSIMDEnabled() + << ";;force_tunnel_mode:" + << player_perf_extension->IsForceTunnelMode() + << ";;Statistics_CPU_usage(%)::whislo:" + << StatisticsWrapper::GetInstance()->cpu_usage.min() + << ",q1:" << cpu_out_percentiles.percentile_25th + << ",med:" << cpu_out_percentiles.median + << ",q3:" << cpu_out_percentiles.percentile_75th << ",average:" + << StatisticsWrapper::GetInstance()->cpu_usage.average() + << ",whishi:" << StatisticsWrapper::GetInstance()->cpu_usage.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance()->cpu_usage.GetNumberOfSamples() + << ";;Statistics_main_web_module(%)::whislo:" + << StatisticsWrapper::GetInstance()->main_web_module.min() + << ",q1:" << main_web_module_out_percentiles.percentile_25th + << ",med:" << main_web_module_out_percentiles.median + << ",q3:" << main_web_module_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->main_web_module.average() + << ",whishi:" + << StatisticsWrapper::GetInstance()->main_web_module.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->main_web_module.GetNumberOfSamples() + << ";;Statistics_network_module(%)::whislo:" + << StatisticsWrapper::GetInstance()->network_module.min() + << ",q1:" << network_module_out_percentiles.percentile_25th + << ",med:" << network_module_out_percentiles.median + << ",q3:" << network_module_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->network_module.average() + << ",whishi:" + << StatisticsWrapper::GetInstance()->network_module.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->network_module.GetNumberOfSamples() + << ";;Statistics_media_pipeline(%)::whislo:" + << StatisticsWrapper::GetInstance()->media_pipeline.min() + << ",q1:" << media_pipeline_out_percentiles.percentile_25th + << ",med:" << media_pipeline_out_percentiles.median + << ",q3:" << media_pipeline_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->media_pipeline.average() + << ",whishi:" + << StatisticsWrapper::GetInstance()->media_pipeline.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->media_pipeline.GetNumberOfSamples() + << ";;Statistics_player_worker(%)::whislo:" + << StatisticsWrapper::GetInstance()->player_worker.min() + << ",q1:" << player_worker_out_percentiles.percentile_25th + << ",med:" << player_worker_out_percentiles.median + << ",q3:" << player_worker_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->player_worker.average() + << ",whishi:" << StatisticsWrapper::GetInstance()->player_worker.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->player_worker.GetNumberOfSamples() + << ";;Statistics_video_decoder(%)::whislo:" + << StatisticsWrapper::GetInstance()->video_decoder.min() + << ",q1:" << video_decoder_out_percentiles.percentile_25th + << ",med:" << video_decoder_out_percentiles.median + << ",q3:" << video_decoder_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->video_decoder.average() + << ",whishi:" << StatisticsWrapper::GetInstance()->video_decoder.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->video_decoder.GetNumberOfSamples() + << ";;Statistics_audio_decoder(%)::whislo:" + << StatisticsWrapper::GetInstance()->audio_decoder.min() + << ",q1:" << audio_decoder_out_percentiles.percentile_25th + << ",med:" << audio_decoder_out_percentiles.median + << ",q3:" << audio_decoder_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->audio_decoder.average() + << ",whishi:" << StatisticsWrapper::GetInstance()->audio_decoder.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->audio_decoder.GetNumberOfSamples() + << ";;Statistics_audio_track_aud(%)::whislo:" + << StatisticsWrapper::GetInstance()->audio_track_aud.min() + << ",q1:" << audio_track_aud_out_percentiles.percentile_25th + << ",med:" << audio_track_aud_out_percentiles.median + << ",q3:" << audio_track_aud_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->audio_track_aud.average() + << ",whishi:" + << StatisticsWrapper::GetInstance()->audio_track_aud.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->audio_track_aud.GetNumberOfSamples() + << ";;Statistics_sbplayer_perf(%)::whislo:" + << StatisticsWrapper::GetInstance()->sbplayer_perf.min() + << ",q1:" << sbplayer_perf_out_percentiles.percentile_25th + << ",med:" << sbplayer_perf_out_percentiles.median + << ",q3:" << sbplayer_perf_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->sbplayer_perf.average() + << ",whishi:" << StatisticsWrapper::GetInstance()->sbplayer_perf.max() + << ",nsamples:" + << StatisticsWrapper::GetInstance() + ->sbplayer_perf.GetNumberOfSamples(); + /*<< ";;Statistics_decoded_speed(fps)::whislo:" + << StatisticsWrapper::GetInstance()->decoded_speed.min() + << ",q1:" << decoded_speed_out_percentiles.percentile_25th + << ",med:" << decoded_speed_out_percentiles.median + << ",q3:" << decoded_speed_out_percentiles.percentile_75th + << ",average:" + << StatisticsWrapper::GetInstance()->decoded_speed.average() + << ",whishi:" << StatisticsWrapper::GetInstance()->decoded_speed.max();*/ + } + } +} + +std::string SbPlayerPerf::GetSbSystemProperty(SbSystemPropertyId property_id) { + char property[1024] = {0}; + std::string result = ""; + if (!SbSystemGetProperty(property_id, property, + SB_ARRAY_SIZE_INT(property))) { + DLOG(FATAL) << "Failed to get kSbSystemPropertyPlatformName."; + return result; + } + result = property; + return result; +} + +SbPlayerPerf::SbPlayerPerf( + SbPlayerInterface* interface, SbPlayer player, + const scoped_refptr& task_runner) + : task_runner_(task_runner), + interface_(interface), + player_(player), + decoded_frames_last_(0), + sb_number_of_processors_(SbSystemGetNumberOfProcessors()), + sb_system_property_brand_name_( + GetSbSystemProperty(kSbSystemPropertyBrandName)), + sb_system_property_model_name_( + GetSbSystemProperty(kSbSystemPropertyModelName)), + sb_system_property_platform_name_( + GetSbSystemProperty(kSbSystemPropertyPlatformName)) {} + +SbPlayerPerf::~SbPlayerPerf() {} + +} // namespace media +} // namespace cobalt diff --git a/cobalt/media/base/sbplayer_perf.h b/cobalt/media/base/sbplayer_perf.h new file mode 100644 index 000000000000..7bdc07655931 --- /dev/null +++ b/cobalt/media/base/sbplayer_perf.h @@ -0,0 +1,74 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_MEDIA_BASE_SBPLAYER_PERF_H_ +#define COBALT_MEDIA_BASE_SBPLAYER_PERF_H_ + +#include +#include +#include + +#include "base/memory/ref_counted.h" +#include "base/task/task_runner.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "cobalt/media/base/sbplayer_interface.h" +#include "starboard/common/system_property.h" +#include "starboard/types.h" + +namespace cobalt { +namespace media { + +class SbPlayerPerf { + public: + // Constructs a measure pipeline that will execute on |task_runner|. + explicit SbPlayerPerf( + SbPlayerInterface* interface, SbPlayer player, + const scoped_refptr& task_runner); + ~SbPlayerPerf(); + + void Start(); + void StartTask(); + void Stop(); + void StopTask(); + + private: + // Refer base/process/process_metrics.h for GetPlatformIndependentCPUUsage() + void GetPlatformIndependentCPUUsage(); + std::string GetSbSystemProperty(SbSystemPropertyId property_id); + + // Message loop used to execute pipeline tasks. It is thread-safe. + scoped_refptr task_runner_; + base::RepeatingTimer timer_; + + SbPlayerInterface* interface_; + SbPlayer player_; + + // TODO(borongchen): ensure when to log CPU statistics, as each video segment + // will Play() once + std::string sb_system_property_brand_name_; + std::string sb_system_property_model_name_; + std::string sb_system_property_platform_name_; + + int sb_number_of_processors_; + Time start_timestamp_; + Time timestamp_last_; + int decoded_frames_last_; +}; + +} // namespace media +} // namespace cobalt + +#endif // COBALT_MEDIA_BASE_SBPLAYER_PERF_H_ diff --git a/cobalt/media/player/web_media_player_impl.cc b/cobalt/media/player/web_media_player_impl.cc index caf64d2fd6e1..ed72840cb19c 100644 --- a/cobalt/media/player/web_media_player_impl.cc +++ b/cobalt/media/player/web_media_player_impl.cc @@ -32,6 +32,7 @@ #include "media/base/limits.h" #include "media/base/timestamp_constants.h" #include "media/filters/chunk_demuxer.h" +#include "starboard/extension/player_perf.h" #include "starboard/system.h" #include "starboard/types.h" #include "ui/gfx/geometry/rect.h" @@ -140,6 +141,17 @@ WebMediaPlayerImpl::WebMediaPlayerImpl( media_log_->AddEvent<::media::MediaLogEvent::kWebMediaPlayerCreated>(); pipeline_thread_.Start(); + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + player_perf_extension->AddThreadID( + pipeline_thread_.thread_name().c_str(), + static_cast(pipeline_thread_.GetThreadId())); + } pipeline_ = new SbPlayerPipeline( interface, window, pipeline_thread_.task_runner(), get_decode_target_graphics_context_provider_func, diff --git a/cobalt/network/network_module.cc b/cobalt/network/network_module.cc index de6b98cc9849..98f6f8f2a120 100644 --- a/cobalt/network/network_module.cc +++ b/cobalt/network/network_module.cc @@ -29,6 +29,7 @@ #include "cobalt/network/network_system.h" #include "cobalt/network/switches.h" #include "net/url_request/static_http_user_agent_settings.h" +#include "starboard/extension/player_perf.h" namespace cobalt { namespace network { @@ -205,6 +206,17 @@ void NetworkModule::Initialize(const std::string& user_agent_string, url_request_context_.get(), thread_.get()); SetEnableQuicFromPersistentSettings(); + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + player_perf_extension->AddThreadID( + thread_->thread_name().c_str(), + static_cast(thread_->GetThreadId())); + } } void NetworkModule::OnCreate( diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc index 39b71ef410ed..3e3e30f9cf18 100644 --- a/cobalt/web/agent.cc +++ b/cobalt/web/agent.cc @@ -46,6 +46,7 @@ #include "cobalt/worker/service_worker_registration.h" #include "cobalt/worker/service_worker_registration_object.h" #include "cobalt/worker/worker_consts.h" +#include "starboard/extension/player_perf.h" namespace cobalt { namespace web { @@ -638,6 +639,17 @@ void Agent::Run(const Options& options, InitializeCallback initialize_callback, task_runner()->PostTask( FROM_HERE, base::Bind(&SignalWaitableEvent, base::Unretained(&destruction_observer_added_))); + const StarboardExtensionPlayerPerfApi* player_perf_extension = + static_cast( + SbSystemGetExtension(kStarboardExtensionPlayerPerfName)); + if (player_perf_extension && + strcmp(player_perf_extension->name, kStarboardExtensionPlayerPerfName) == + 0 && + player_perf_extension->version >= 1) { + player_perf_extension->AddThreadID( + thread_.thread_name().c_str(), + static_cast(thread_.GetThreadId())); + } } void Agent::InitializeTaskInThread(const Options& options, diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index 8c219b391aec..234bedf6c41c 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn @@ -376,6 +376,8 @@ static_library("starboard_platform") { "player_create.cc", "player_destroy.cc", "player_get_preferred_output_mode.cc", + "player_perf.cc", + "player_perf.h", "player_set_bounds.cc", "player_set_max_video_input_size.cc", "player_set_max_video_input_size.h", diff --git a/starboard/android/shared/audio_track_audio_sink_type.cc b/starboard/android/shared/audio_track_audio_sink_type.cc index 9788b5fb375d..2d19cf1a91c4 100644 --- a/starboard/android/shared/audio_track_audio_sink_type.cc +++ b/starboard/android/shared/audio_track_audio_sink_type.cc @@ -20,6 +20,7 @@ #include #include "starboard/android/shared/media_capabilities_cache.h" +#include "starboard/android/shared/player_perf.h" #include "starboard/common/string.h" #include "starboard/common/time.h" #include "starboard/shared/pthread/thread_create_priority.h" @@ -162,6 +163,8 @@ AudioTrackAudioSink::AudioTrackAudioSink( return; } + SetAudioTrackBridge(&bridge_); + pthread_create(&audio_out_thread_, nullptr, &AudioTrackAudioSink::ThreadEntryPoint, this); SB_DCHECK(audio_out_thread_ != 0); @@ -193,6 +196,9 @@ void* AudioTrackAudioSink::ThreadEntryPoint(void* context) { ::starboard::shared::pthread::ThreadSetPriority(kSbThreadPriorityRealTime); AudioTrackAudioSink* sink = reinterpret_cast(context); + char name[128] = {0}; + pthread_getname_np(pthread_self(), name, SB_ARRAY_SIZE_INT(name)); + AddThreadIDInternal(name, static_cast(SbThreadGetId())); sink->AudioThreadFunc(); return NULL; diff --git a/starboard/android/shared/media_decoder.cc b/starboard/android/shared/media_decoder.cc index 5411792745c2..dd99a0438dba 100644 --- a/starboard/android/shared/media_decoder.cc +++ b/starboard/android/shared/media_decoder.cc @@ -19,6 +19,7 @@ #include "starboard/android/shared/jni_env_ext.h" #include "starboard/android/shared/jni_utils.h" #include "starboard/android/shared/media_common.h" +#include "starboard/android/shared/player_perf.h" #include "starboard/audio_sink.h" #include "starboard/common/log.h" #include "starboard/common/string.h" @@ -198,6 +199,9 @@ void MediaDecoder::WriteInputBuffers(const InputBuffers& input_buffers) { } if (decoder_thread_ == 0) { + char name[128] = {0}; + pthread_getname_np(pthread_self(), name, SB_ARRAY_SIZE_INT(name)); + AddThreadIDInternal(name, static_cast(SbThreadGetId())); pthread_create(&decoder_thread_, nullptr, &MediaDecoder::DecoderThreadEntryPoint, this); SB_DCHECK(decoder_thread_ != 0); @@ -242,6 +246,9 @@ void* MediaDecoder::DecoderThreadEntryPoint(void* context) { } else { ::starboard::shared::pthread::ThreadSetPriority(kSbThreadPriorityHigh); } + char name[128] = {0}; + pthread_getname_np(pthread_self(), name, SB_ARRAY_SIZE_INT(name)); + AddThreadIDInternal(name, static_cast(SbThreadGetId())); decoder->DecoderThreadFunc(); return NULL; diff --git a/starboard/android/shared/player_components_factory.h b/starboard/android/shared/player_components_factory.h index 536b404c67ea..d143c4fedeb6 100644 --- a/starboard/android/shared/player_components_factory.h +++ b/starboard/android/shared/player_components_factory.h @@ -26,6 +26,7 @@ #include "starboard/android/shared/jni_utils.h" #include "starboard/android/shared/media_capabilities_cache.h" #include "starboard/android/shared/media_common.h" +#include "starboard/android/shared/player_perf.h" #include "starboard/android/shared/video_decoder.h" #include "starboard/atomic.h" #include "starboard/common/log.h" @@ -497,6 +498,8 @@ class PlayerComponentsFactory : public starboard::shared::starboard::player:: } } + SetMediaVideoDecoderCodec(creation_parameters.video_codec()); + SetMediaAudioDecoderCodec(creation_parameters.audio_codec()); return true; } diff --git a/starboard/android/shared/player_perf.cc b/starboard/android/shared/player_perf.cc new file mode 100644 index 000000000000..3caad0447607 --- /dev/null +++ b/starboard/android/shared/player_perf.cc @@ -0,0 +1,362 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/android/shared/player_perf.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "starboard/android/shared/player_components_factory.h" +#include "starboard/common/log.h" +#include "starboard/common/mutex.h" +#include "starboard/common/time.h" +#include "starboard/configuration.h" +#include "starboard/extension/player_perf.h" +#include "starboard/file.h" +#include "starboard/media.h" + +namespace starboard { +namespace android { +namespace shared { + +namespace { + +// Definitions of any functions included as components in the extension +// are added here. +const char kProcDir[] = "/proc/"; +const char kStatFile[] = "/stat"; +const char kProcTaskDir[] = "/proc/self/task/"; +const int64_t kMaxFileSizeBytes = 512; + +std::unordered_map> + map_thread_ids; + +Mutex mutex_; + +// Counting the times of should_be_paused +int should_be_paused_count_ = 0; + +// Used to store the previous times and CPU usage counts so we can +// compute the CPU usage between calls. +int64_t last_cpu_time_; +int64_t last_cumulative_cpu_ = 0; + +AudioTrackBridge* audio_track_bridge_ = nullptr; +SbMediaVideoCodec media_video_codec_ = kSbMediaVideoCodecNone; +SbMediaAudioCodec media_audio_codec_ = kSbMediaAudioCodecNone; + +// Fields from /proc//stat, 0-based. See man 5 proc. +// If the ordering ever changes, carefully review functions that use these +// values. +enum ProcStatsFields { + VM_COMM = 1, // Filename of executable, without parentheses. + VM_STATE = 2, // Letter indicating the state of the process. + VM_PPID = 3, // PID of the parent. + VM_PGRP = 4, // Process group id. + VM_MINFLT = 9, // Minor page fault count excluding children. + VM_MAJFLT = 11, // Major page fault count excluding children. + VM_UTIME = 13, // Time scheduled in user mode in clock ticks. + VM_STIME = 14, // Time scheduled in kernel mode in clock ticks. + VM_NUMTHREADS = 19, // Number of threads. + VM_STARTTIME = 21, // The time the process started in clock ticks. + VM_VSIZE = 22, // Virtual memory size in bytes. + VM_RSS = 23, // Resident Set Size in pages. +}; + +std::string GetProcPidDir(pid_t pid) { + std::string proc_pid_dir(kProcDir); + proc_pid_dir.append(std::to_string(pid)); + return proc_pid_dir; +} + +std::string GetProcTaskDir(int32_t thread_id) { + std::string proc_task_dir(kProcTaskDir); + proc_task_dir.append(std::to_string(thread_id)); + return proc_task_dir; +} + +bool ReadProcFile(const std::string& file, std::string* buffer) { + SbFile sb_file = + SbFileOpen(file.c_str(), kSbFileOpenOnly | kSbFileRead, nullptr, nullptr); + if (!SbFileIsValid(sb_file)) { + SB_DLOG(WARNING) << "Failed to read " << file.c_str(); + return false; + } + int bytes_read = SbFileRead(sb_file, reinterpret_cast(buffer->data()), + kMaxFileSizeBytes); + buffer->data()[bytes_read - 1] = '\0'; + if (bytes_read < kMaxFileSizeBytes) { + buffer->erase(bytes_read, kMaxFileSizeBytes - bytes_read); + } + SbFileClose(sb_file); + return !buffer->empty(); +} + +bool ReadProcStats(std::string& stat_file, std::string* buffer) { + return ReadProcFile(stat_file, buffer); +} + +bool ParseProcStats(const std::string& stats_data, + std::vector* proc_stats) { + // |stats_data| may be empty if the process disappeared somehow. + // e.g. http://crbug.com/145811 + if (stats_data.empty()) + return false; + + // The stat file is formatted as: + // pid (process name) data1 data2 .... dataN + // Look for the closing paren by scanning backwards, to avoid being fooled by + // processes with ')' in the name. + size_t open_parens_idx = stats_data.find(" ("); + size_t close_parens_idx = stats_data.rfind(") "); + if (open_parens_idx == std::string::npos || + close_parens_idx == std::string::npos || + open_parens_idx > close_parens_idx) { + SB_DLOG(WARNING) << "Failed to find matched parens in '" << stats_data + << "'"; + SB_NOTREACHED(); + return false; + } + open_parens_idx++; + + proc_stats->clear(); + // PID. + proc_stats->push_back(stats_data.substr(0, open_parens_idx)); + // Process name without parentheses. + proc_stats->push_back(stats_data.substr( + open_parens_idx + 1, close_parens_idx - (open_parens_idx + 1))); + + // Split the rest. + std::string other_stats = stats_data.substr(close_parens_idx + 2); + std::string delimiter = " "; + size_t pos = 0; + std::string token; + while ((pos = other_stats.find(delimiter)) != std::string::npos) { + token = other_stats.substr(0, pos); + proc_stats->push_back(token); + other_stats.erase(0, pos + delimiter.length()); + } + proc_stats->push_back(other_stats); + return true; +} + +int64_t GetProcStatsFieldAsInt64(const std::vector& proc_stats, + ProcStatsFields field_num) { + SB_DCHECK(field_num > VM_PPID); + SB_CHECK(static_cast(field_num) <= proc_stats.size()); + + int64_t value = static_cast(std::stoll(proc_stats[field_num])); + return value; +} + +// Get the total CPU of a single process. Return value is number of jiffies +// on success or -1 on error. +int64_t GetProcessCPU(std::string& stat_file) { + std::string buffer(kMaxFileSizeBytes, ' '); + std::vector proc_stats; + if (!ReadProcStats(stat_file, &buffer) || + !ParseProcStats(buffer, &proc_stats)) { + return -1; + } + + int64_t total_cpu = GetProcStatsFieldAsInt64(proc_stats, VM_UTIME) + + GetProcStatsFieldAsInt64(proc_stats, VM_STIME); + return total_cpu; +} + +int64_t ClockTicksToTimeDelta(int64_t clock_ticks) { + // This queries the /proc-specific scaling factor which is + // conceptually the system hertz. To dump this value on another + // system, try + // od -t dL /proc/self/auxv + // and look for the number after 17 in the output; mine is + // 0000040 17 100 3 134512692 + // which means the answer is 100. + // It may be the case that this value is always 100. + static const int64_t kHertz = sysconf(_SC_CLK_TCK); + + return 1'000'000 * clock_ticks / kHertz; +} + +int64_t GetCumulativeCPUUsage(std::string& stat_file) { + return ClockTicksToTimeDelta(GetProcessCPU(stat_file)); +} + +std::string exec(const char* cmd) { + std::array buffer; + std::string result; + std::unique_ptr pipe(popen(cmd, "r"), pclose); + if (!pipe) { + SB_LOG(ERROR) << "popen() failed!"; + return result; + } + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + return result; +} + +std::unordered_map GetPlatformIndependentCPUUsage() { + std::unordered_map map_cumulative_cpu; + std::string pid_stat_file = GetProcPidDir(getpid()).append(kStatFile); + int64_t cumulative_cpu = GetCumulativeCPUUsage(pid_stat_file); + int64_t time = CurrentPosixTime(); + + if (last_cumulative_cpu_ == 0) { + // First call, just set the last values. + last_cumulative_cpu_ = cumulative_cpu; + last_cpu_time_ = time; + map_cumulative_cpu["system"] = 0; + return map_cumulative_cpu; + } + + int64_t system_time_delta = cumulative_cpu - last_cumulative_cpu_; + int64_t time_delta = time - last_cpu_time_; + SB_DCHECK(time_delta != 0); + if (time_delta == 0) { + map_cumulative_cpu["system"] = 0; + return map_cumulative_cpu; + } + + last_cumulative_cpu_ = cumulative_cpu; + last_cpu_time_ = time; + + double cpu_usage = 100.0 * static_cast(system_time_delta) / + static_cast(time_delta); + map_cumulative_cpu["system"] = cpu_usage; + { + ScopedLock scoped_lock(mutex_); + for (auto element : map_thread_ids) { + int64_t task_time_delta = 0; + for (auto value : element.second) { + std::string task_stat_file = + GetProcTaskDir(value.first).append(kStatFile); + int64_t cumulative_cpu_task = GetCumulativeCPUUsage(task_stat_file); + if (cumulative_cpu_task >= 0) { + if (value.second != 0) { + task_time_delta += (cumulative_cpu_task - value.second); + } + map_thread_ids[element.first][value.first] = cumulative_cpu_task; + } + } + double cpu_usage = 100.0 * static_cast(task_time_delta) / + static_cast(time_delta); + map_cumulative_cpu[element.first] = cpu_usage; + } + } + return map_cumulative_cpu; +} + +int GetAudioUnderrunCount() { + if (audio_track_bridge_ != nullptr && audio_track_bridge_->is_valid()) { + return audio_track_bridge_->GetUnderrunCount(); + } + return -1; +} + +bool IsSIMDEnabled() { +#if defined(USE_NEON) + return true; +#else // defined(USE_NEON) + return false; +#endif // defined(USE_NEON) +} + +bool IsForceTunnelMode() { + return starboard::android::shared::kForceTunnelMode; +} + +const char* GetCurrentMediaAudioCodecName() { + return GetMediaAudioCodecName(media_audio_codec_); +} + +const char* GetCurrentMediaVideoCodecName() { + return GetMediaVideoCodecName(media_video_codec_); +} + +int GetCountShouldBePaused() { + return should_be_paused_count_; +} + +void IncreaseCountShouldBePaused() { + should_be_paused_count_ += 1; + SB_LOG(ERROR) << "Brown paused"; +} + +void AddThreadID(const char* thread_name, int32_t thread_id) { + AddThreadIDInternal(thread_name, thread_id); +} + +const StarboardExtensionPlayerPerfApi kGetPlayerPerfApi = { + kStarboardExtensionPlayerPerfName, + 1, + &GetPlatformIndependentCPUUsage, + &GetAudioUnderrunCount, + &IsSIMDEnabled, + &IsForceTunnelMode, + &GetCurrentMediaAudioCodecName, + &GetCurrentMediaVideoCodecName, + &IncreaseCountShouldBePaused, + &GetCountShouldBePaused, + &AddThreadID, +}; + +} // namespace + +void SetAudioTrackBridge(AudioTrackBridge* audio_track_bridge) { + audio_track_bridge_ = audio_track_bridge; +} + +void SetMediaVideoDecoderCodec(SbMediaVideoCodec codec) { + media_video_codec_ = codec; +} + +void SetMediaAudioDecoderCodec(SbMediaAudioCodec codec) { + media_audio_codec_ = codec; +} + +const void* GetPlayerPerfApi() { + return &kGetPlayerPerfApi; +} + +void AddThreadIDInternal(const char* thread_name, int32_t thread_id) { + // SB_LOG(ERROR) << "Brown add " << thread_name << " (id " << thread_id << + // ")."; + ScopedLock scoped_lock(mutex_); + auto found_thread_name = map_thread_ids.find(thread_name); + if (found_thread_name == map_thread_ids.end()) { + std::unordered_map value_tmp; + value_tmp[thread_id] = 0; + map_thread_ids[thread_name] = value_tmp; + } else { + auto found_thread_id = map_thread_ids[thread_name].find(thread_id); + if (found_thread_id == map_thread_ids[thread_name].end()) { + map_thread_ids[thread_name][thread_id] = 0; + } + } +} + +} // namespace shared +} // namespace android +} // namespace starboard diff --git a/starboard/android/shared/player_perf.h b/starboard/android/shared/player_perf.h new file mode 100644 index 000000000000..a12be57a1ccc --- /dev/null +++ b/starboard/android/shared/player_perf.h @@ -0,0 +1,35 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_ANDROID_SHARED_PLAYER_PERF_H_ +#define STARBOARD_ANDROID_SHARED_PLAYER_PERF_H_ + +#include "starboard/android/shared/audio_track_bridge.h" + +namespace starboard { +namespace android { +namespace shared { + +const void* GetPlayerPerfApi(); + +void SetAudioTrackBridge(AudioTrackBridge* audio_track_bridge); +void SetMediaVideoDecoderCodec(SbMediaVideoCodec codec); +void SetMediaAudioDecoderCodec(SbMediaAudioCodec codec); +void AddThreadIDInternal(const char* thread_name, int32_t thread_id); + +} // namespace shared +} // namespace android +} // namespace starboard + +#endif // STARBOARD_ANDROID_SHARED_PLAYER_PERF_H_ diff --git a/starboard/android/shared/system_get_extensions.cc b/starboard/android/shared/system_get_extensions.cc index 480a7323f98c..0b10f00bb113 100644 --- a/starboard/android/shared/system_get_extensions.cc +++ b/starboard/android/shared/system_get_extensions.cc @@ -19,6 +19,7 @@ #include "starboard/android/shared/graphics.h" #include "starboard/android/shared/platform_info.h" #include "starboard/android/shared/platform_service.h" +#include "starboard/android/shared/player_perf.h" #include "starboard/android/shared/player_set_max_video_input_size.h" #include "starboard/common/log.h" #include "starboard/common/string.h" @@ -36,6 +37,7 @@ #include "starboard/extension/media_session.h" #include "starboard/extension/platform_info.h" #include "starboard/extension/platform_service.h" +#include "starboard/extension/player_perf.h" #include "starboard/extension/player_set_max_video_input_size.h" const void* SbSystemGetExtension(const char* name) { @@ -80,5 +82,8 @@ const void* SbSystemGetExtension(const char* name) { return starboard::shared::starboard::GetLoaderAppMetricsApi(); } #endif + if (strcmp(name, kStarboardExtensionPlayerPerfName) == 0) { + return starboard::android::shared::GetPlayerPerfApi(); + } return NULL; } diff --git a/starboard/extension/extension_test.cc b/starboard/extension/extension_test.cc index 551e834860cc..e27b031100d9 100644 --- a/starboard/extension/extension_test.cc +++ b/starboard/extension/extension_test.cc @@ -30,6 +30,7 @@ #include "starboard/extension/platform_info.h" #include "starboard/extension/platform_service.h" #include "starboard/extension/player_configuration.h" +#include "starboard/extension/player_perf.h" #include "starboard/extension/player_set_max_video_input_size.h" #include "starboard/extension/time_zone.h" #include "starboard/extension/updater_notification.h" @@ -564,5 +565,33 @@ TEST(ExtensionTest, PlayerConfiguration) { } } +TEST(ExtensionTest, PlayerPerf) { + typedef StarboardExtensionPlayerPerfApi ExtensionApi; + const char* kExtensionName = kStarboardExtensionPlayerPerfName; + + const ExtensionApi* extension_api = + static_cast(SbSystemGetExtension(kExtensionName)); + if (!extension_api) { + return; + } + + EXPECT_STREQ(extension_api->name, kExtensionName); + EXPECT_EQ(extension_api->version, 1u); + EXPECT_NE(extension_api->GetPlatformIndependentCPUUsage, nullptr); + EXPECT_NE(extension_api->GetAudioUnderrunCount, nullptr); + EXPECT_NE(extension_api->IsSIMDEnabled, nullptr); + EXPECT_NE(extension_api->IsForceTunnelMode, nullptr); + EXPECT_NE(extension_api->GetCurrentMediaAudioCodecName, nullptr); + EXPECT_NE(extension_api->GetCurrentMediaVideoCodecName, nullptr); + EXPECT_NE(extension_api->IncreaseCountShouldBePaused, nullptr); + EXPECT_NE(extension_api->GetCountShouldBePaused, nullptr); + EXPECT_NE(extension_api->AddThreadID, nullptr); + + const ExtensionApi* second_extension_api = + static_cast(SbSystemGetExtension(kExtensionName)); + EXPECT_EQ(second_extension_api, extension_api) + << "Extension struct should be a singleton"; +} + } // namespace extension } // namespace starboard diff --git a/starboard/extension/player_perf.h b/starboard/extension/player_perf.h new file mode 100644 index 000000000000..355d83126869 --- /dev/null +++ b/starboard/extension/player_perf.h @@ -0,0 +1,60 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_EXTENSION_PLAYER_PERF_H_ +#define STARBOARD_EXTENSION_PLAYER_PERF_H_ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define kStarboardExtensionPlayerPerfName "dev.starboard.extension.PlayerPerf" + +typedef struct StarboardExtensionPlayerPerfApi { + // Name should be the string + // |kStarboardExtensionPlayerPerfName|. This helps to validate + // that the extension API is correct. + const char* name; + + // This specifies the version of the API that is implemented. + uint32_t version; + + // The fields below this point were added in version 1 or later. + + // Returns the percentage of time spent executing, across all threads of the + // process, in the interval since the last time the method was called. + // For more detail, refer base/process/process_metrics.h. + std::unordered_map (*GetPlatformIndependentCPUUsage)(); + + int (*GetAudioUnderrunCount)(); + bool (*IsSIMDEnabled)(); + bool (*IsForceTunnelMode)(); + const char* (*GetCurrentMediaAudioCodecName)(); + const char* (*GetCurrentMediaVideoCodecName)(); + void (*IncreaseCountShouldBePaused)(); + int (*GetCountShouldBePaused)(); + void (*AddThreadID)(const char* thread_name, int32_t thread_id); +} StarboardExtensionPlayerPerfApi; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // STARBOARD_EXTENSION_PLAYER_PERF_H_