From 6a891ea37c1f808d3e5a75124587bebd97e0ceae Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Sat, 27 Oct 2018 04:59:08 +0200 Subject: [PATCH] Core/Analytics: add support for performance sampling Samples are pushed to the analytics module every frame but only sent once every ~15min. We send data for 100 frames at a time. --- Source/Core/Core/Analytics.cpp | 67 ++++++++++++++++++++++++++++++++++ Source/Core/Core/Analytics.h | 30 +++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/Source/Core/Core/Analytics.cpp b/Source/Core/Core/Analytics.cpp index 43c703a64d..63b6d6c481 100644 --- a/Source/Core/Core/Analytics.cpp +++ b/Source/Core/Core/Analytics.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #if defined(_WIN32) #include @@ -20,6 +21,7 @@ #include "Common/CommonTypes.h" #include "Common/Random.h" #include "Common/StringUtil.h" +#include "Common/Timer.h" #include "Common/Version.h" #include "Core/ConfigManager.h" #include "Core/HW/GCPad.h" @@ -128,6 +130,71 @@ void DolphinAnalytics::ReportGameStart() Common::AnalyticsReportBuilder builder(m_per_game_builder); builder.AddData("type", "game-start"); Send(builder); + + InitializePerformanceSampling(); +} + +void DolphinAnalytics::ReportPerformanceInfo(PerformanceSample&& sample) +{ + if (ShouldStartPerformanceSampling()) + { + m_sampling_performance_info = true; + } + + if (m_sampling_performance_info) + { + m_performance_samples.emplace_back(std::move(sample)); + } + + if (m_performance_samples.size() >= NUM_PERFORMANCE_SAMPLES_PER_REPORT) + { + std::vector speed_times_1000(m_performance_samples.size()); + std::vector num_prims(m_performance_samples.size()); + std::vector num_draw_calls(m_performance_samples.size()); + for (size_t i = 0; i < m_performance_samples.size(); ++i) + { + speed_times_1000[i] = static_cast(m_performance_samples[i].speed_ratio * 1000); + num_prims[i] = m_performance_samples[i].num_prims; + num_draw_calls[i] = m_performance_samples[i].num_draw_calls; + } + + // The per game builder should already exist -- there is no way we can be reporting performance + // info without a game start event having been generated. + Common::AnalyticsReportBuilder builder(m_per_game_builder); + builder.AddData("type", "performance"); + builder.AddData("speed", speed_times_1000); + builder.AddData("prims", num_prims); + builder.AddData("draw-calls", num_draw_calls); + + Send(builder); + + // Clear up and stop sampling until next time ShouldStartPerformanceSampling() says so. + m_performance_samples.clear(); + m_sampling_performance_info = false; + } +} + +void DolphinAnalytics::InitializePerformanceSampling() +{ + m_performance_samples.clear(); + m_sampling_performance_info = false; + + u64 wait_us = + PERFORMANCE_SAMPLING_INITIAL_WAIT_TIME_SECS * 1000000 + + Common::Random::GenerateValue() % (PERFORMANCE_SAMPLING_WAIT_TIME_JITTER_SECS * 1000000); + m_sampling_next_start_us = Common::Timer::GetTimeUs() + wait_us; +} + +bool DolphinAnalytics::ShouldStartPerformanceSampling() +{ + if (Common::Timer::GetTimeUs() < m_sampling_next_start_us) + return false; + + u64 wait_us = + PERFORMANCE_SAMPLING_INTERVAL_SECS * 1000000 + + Common::Random::GenerateValue() % (PERFORMANCE_SAMPLING_WAIT_TIME_JITTER_SECS * 1000000); + m_sampling_next_start_us = Common::Timer::GetTimeUs() + wait_us; + return true; } void DolphinAnalytics::MakeBaseBuilder() diff --git a/Source/Core/Core/Analytics.h b/Source/Core/Core/Analytics.h index 8c2415b92d..c0725efd51 100644 --- a/Source/Core/Core/Analytics.h +++ b/Source/Core/Core/Analytics.h @@ -7,8 +7,10 @@ #include #include #include +#include #include "Common/Analytics.h" +#include "Common/CommonTypes.h" #if defined(ANDROID) #include @@ -40,6 +42,18 @@ public: // per-game base data. void ReportGameStart(); + struct PerformanceSample + { + double speed_ratio; // See SystemTimers::GetEstimatedEmulationPerformance(). + int num_prims; + int num_draw_calls; + }; + // Reports performance information. This method performs its own throttling / aggregation -- + // calling it does not guarantee when a report will actually be sent. + // + // This method is NOT thread-safe. + void ReportPerformanceInfo(PerformanceSample&& sample); + // Forward Send method calls to the reporter. template void Send(T report) @@ -63,6 +77,22 @@ private: // values created by MakeUniqueId. std::string m_unique_id; + // Performance sampling configuration constants. + // + // 5min after startup + rand(0, 3min) jitter time, collect performance for 100 frames in a row. + // Repeat collection after 30min + rand(0, 3min). + static constexpr int NUM_PERFORMANCE_SAMPLES_PER_REPORT = 100; + static constexpr int PERFORMANCE_SAMPLING_INITIAL_WAIT_TIME_SECS = 300; + static constexpr int PERFORMANCE_SAMPLING_WAIT_TIME_JITTER_SECS = 180; + static constexpr int PERFORMANCE_SAMPLING_INTERVAL_SECS = 1800; + + // Performance sampling state & internal helpers. + void InitializePerformanceSampling(); // Called on game start / title switch. + bool ShouldStartPerformanceSampling(); + u64 m_sampling_next_start_us; // Next timestamp (in us) at which to trigger sampling. + bool m_sampling_performance_info = false; // Whether we are currently collecting samples. + std::vector m_performance_samples; + // Builder that contains all non variable data that should be sent with all // reports. Common::AnalyticsReportBuilder m_base_builder;