diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index e411239d17..463736ac08 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -512,6 +512,9 @@ add_library(core NetworkCaptureLogger.h PatchEngine.cpp PatchEngine.h + PerformanceSample.h + PerformanceSampleAggregator.cpp + PerformanceSampleAggregator.h PowerPC/BreakPoints.cpp PowerPC/BreakPoints.h PowerPC/CachedInterpreter/CachedInterpreter_Disassembler.cpp diff --git a/Source/Core/Core/DolphinAnalytics.cpp b/Source/Core/Core/DolphinAnalytics.cpp index 936cd7191b..ec12be3cbc 100644 --- a/Source/Core/Core/DolphinAnalytics.cpp +++ b/Source/Core/Core/DolphinAnalytics.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include @@ -17,10 +16,6 @@ #include "Common/WindowsRegistry.h" #endif -#if defined(ANDROID) -#include -#endif - #if defined(__APPLE__) #include "Common/CommonFuncs.h" #endif @@ -31,7 +26,6 @@ #include "Common/Config/Config.h" #include "Common/Crypto/SHA1.h" #include "Common/Random.h" -#include "Common/Timer.h" #include "Common/Version.h" #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" @@ -39,17 +33,14 @@ #include "Core/HW/GCPad.h" #include "Core/Movie.h" #include "Core/NetPlayProto.h" + #include "Core/System.h" #include "InputCommon/GCAdapter.h" #include "InputCommon/InputConfig.h" + #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" -namespace -{ -constexpr char ANALYTICS_ENDPOINT[] = "https://analytics.dolphin-emu.org/report"; -} // namespace - #if defined(ANDROID) static std::function s_get_val_func; void DolphinAnalytics::AndroidSetGetValFunc(std::function func) @@ -58,6 +49,79 @@ void DolphinAnalytics::AndroidSetGetValFunc(std::functionAddData("version-desc", Common::GetScmDescStr()); + builder->AddData("version-hash", Common::GetScmRevGitStr()); + builder->AddData("version-branch", Common::GetScmBranchStr()); + builder->AddData("version-dist", Common::GetScmDistributorStr()); +} + +void AddAutoUpdateInformationToReportBuilder(Common::AnalyticsReportBuilder* builder) +{ + builder->AddData("update-track", Config::Get(Config::MAIN_AUTOUPDATE_UPDATE_TRACK)); +} + +void AddCPUInformationToReportBuilder(Common::AnalyticsReportBuilder* builder) +{ + builder->AddData("cpu-summary", cpu_info.Summarize()); +} + +#if defined(_WIN32) +void AddWindowsInformationToReportBuilder(Common::AnalyticsReportBuilder* builder) +{ + const auto winver = WindowsRegistry::GetOSVersion(); + builder->AddData("win-ver-major", static_cast(winver.dwMajorVersion)); + builder->AddData("win-ver-minor", static_cast(winver.dwMinorVersion)); + builder->AddData("win-ver-build", static_cast(winver.dwBuildNumber)); +} +#elif defined(ANDROID) +void AddAndroidInformationToReportBuilder(Common::AnalyticsReportBuilder* builder) +{ + builder->AddData("android-manufacturer", s_get_val_func("DEVICE_MANUFACTURER")); + builder->AddData("android-model", s_get_val_func("DEVICE_MODEL")); + builder->AddData("android-version", s_get_val_func("DEVICE_OS")); +} +#elif defined(__APPLE__) +void AddMacOSInformationToReportBuilder(Common::AnalyticsReportBuilder* builder) +{ + Common::MacOSVersion version = Common::GetMacOSVersion(); + builder->AddData("osx-ver-major", version.major); + builder->AddData("osx-ver-minor", version.minor); + builder->AddData("osx-ver-bugfix", version.patch); +} +#endif + +void AddPlatformInformationToReportBuilder(Common::AnalyticsReportBuilder* builder) +{ +#if defined(_WIN32) + builder->AddData("os-type", "windows"); + AddWindowsInformationToReportBuilder(builder); +#elif defined(ANDROID) + builder->AddData("os-type", "android"); + AddAndroidInformationToReportBuilder(builder); +#elif defined(__APPLE__) + builder->AddData("os-type", "osx"); + AddMacOSInformationToReportBuilder(builder); +#elif defined(__linux__) + builder->AddData("os-type", "linux"); +#elif defined(__FreeBSD__) + builder->AddData("os-type", "freebsd"); +#elif defined(__OpenBSD__) + builder->AddData("os-type", "openbsd"); +#elif defined(__NetBSD__) + builder->AddData("os-type", "netbsd"); +#elif defined(__HAIKU__) + builder->AddData("os-type", "haiku"); +#else + builder->AddData("os-type", "unknown"); +#endif +} +} // namespace + +// Under arm64, we need to call objc_msgSend to receive a struct. DolphinAnalytics::DolphinAnalytics() { m_last_analytics_enabled = Config::Get(Config::MAIN_ANALYTICS_ENABLED); @@ -88,13 +152,14 @@ DolphinAnalytics& DolphinAnalytics::Instance() void DolphinAnalytics::ReloadConfig() { - std::lock_guard lk{m_reporter_mutex}; + const std::lock_guard lk{m_reporter_mutex}; // Install the HTTP backend if analytics support is enabled. std::unique_ptr new_backend; if (m_last_analytics_enabled) { - new_backend = std::make_unique(ANALYTICS_ENDPOINT); + constexpr char analytics_endpoint[] = "https://analytics.dolphin-emu.org/report"; + new_backend = std::make_unique(analytics_endpoint); } m_reporter.SetBackend(std::move(new_backend)); @@ -150,7 +215,7 @@ void DolphinAnalytics::ReportGameStart() // Reset per-game state. m_reported_quirks.fill(false); - InitializePerformanceSampling(); + m_sample_aggregator.InitializePerformanceSampling(); } // Keep in sync with enum class GameQuirk definition. @@ -194,7 +259,7 @@ static_assert(GAME_QUIRKS_NAMES.size() == static_cast(GameQuirk::Count), void DolphinAnalytics::ReportGameQuirk(GameQuirk quirk) { - u32 quirk_idx = static_cast(quirk); + const u32 quirk_idx = static_cast(quirk); // Only report once per run. if (m_reported_quirks[quirk_idx]) @@ -209,118 +274,34 @@ void DolphinAnalytics::ReportGameQuirk(GameQuirk quirk) void DolphinAnalytics::ReportPerformanceInfo(PerformanceSample sample) { - if (ShouldStartPerformanceSampling()) + m_sample_aggregator.AddSampleIfSamplingInProgress(sample); + const std::optional report_optional = + m_sample_aggregator.PopReportIfComplete(); + if (!report_optional) { - m_sampling_performance_info = true; + return; } + const PerformanceSampleAggregator::CompletedReport& report = *report_optional; - if (m_sampling_performance_info) - { - m_performance_samples.emplace_back(sample); - } + // 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", report.speed); + builder.AddData("prims", report.primitives); + builder.AddData("draw-calls", report.draw_calls); - 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::NowUs() + wait_us; -} - -bool DolphinAnalytics::ShouldStartPerformanceSampling() -{ - if (Common::Timer::NowUs() < 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::NowUs() + wait_us; - return true; + Send(builder); } void DolphinAnalytics::MakeBaseBuilder() { - Common::AnalyticsReportBuilder builder; + m_base_builder = Common::AnalyticsReportBuilder(); - // Version information. - builder.AddData("version-desc", Common::GetScmDescStr()); - builder.AddData("version-hash", Common::GetScmRevGitStr()); - builder.AddData("version-branch", Common::GetScmBranchStr()); - builder.AddData("version-dist", Common::GetScmDistributorStr()); - - // Auto-Update information. - builder.AddData("update-track", Config::Get(Config::MAIN_AUTOUPDATE_UPDATE_TRACK)); - - // CPU information. - builder.AddData("cpu-summary", cpu_info.Summarize()); - -// OS information. -#if defined(_WIN32) - builder.AddData("os-type", "windows"); - - const auto winver = WindowsRegistry::GetOSVersion(); - builder.AddData("win-ver-major", static_cast(winver.dwMajorVersion)); - builder.AddData("win-ver-minor", static_cast(winver.dwMinorVersion)); - builder.AddData("win-ver-build", static_cast(winver.dwBuildNumber)); -#elif defined(ANDROID) - builder.AddData("os-type", "android"); - builder.AddData("android-manufacturer", s_get_val_func("DEVICE_MANUFACTURER")); - builder.AddData("android-model", s_get_val_func("DEVICE_MODEL")); - builder.AddData("android-version", s_get_val_func("DEVICE_OS")); -#elif defined(__APPLE__) - builder.AddData("os-type", "osx"); - - Common::MacOSVersion version = Common::GetMacOSVersion(); - builder.AddData("osx-ver-major", version.major); - builder.AddData("osx-ver-minor", version.minor); - builder.AddData("osx-ver-bugfix", version.patch); -#elif defined(__linux__) - builder.AddData("os-type", "linux"); -#elif defined(__FreeBSD__) - builder.AddData("os-type", "freebsd"); -#elif defined(__OpenBSD__) - builder.AddData("os-type", "openbsd"); -#elif defined(__NetBSD__) - builder.AddData("os-type", "netbsd"); -#elif defined(__HAIKU__) - builder.AddData("os-type", "haiku"); -#else - builder.AddData("os-type", "unknown"); -#endif - - m_base_builder = builder; + AddVersionInformationToReportBuilder(&m_base_builder); + AddAutoUpdateInformationToReportBuilder(&m_base_builder); + AddCPUInformationToReportBuilder(&m_base_builder); + AddPlatformInformationToReportBuilder(&m_base_builder); } static const char* GetShaderCompilationMode() @@ -347,12 +328,13 @@ static bool UseVertexRounding() void DolphinAnalytics::MakePerGameBuilder() { Common::AnalyticsReportBuilder builder(m_base_builder); + const SConfig& config = SConfig::GetInstance(); // Gameid. - builder.AddData("gameid", SConfig::GetInstance().GetGameID()); + builder.AddData("gameid", config.GetGameID()); // Unique id bound to the gameid. - builder.AddData("id", MakeUniqueId(SConfig::GetInstance().GetGameID())); + builder.AddData("id", MakeUniqueId(config.GetGameID())); // Configuration. builder.AddData("cfg-dsp-hle", Config::Get(Config::MAIN_DSP_HLE)); diff --git a/Source/Core/Core/DolphinAnalytics.h b/Source/Core/Core/DolphinAnalytics.h index 252207e2d1..d72d24b2a1 100644 --- a/Source/Core/Core/DolphinAnalytics.h +++ b/Source/Core/Core/DolphinAnalytics.h @@ -7,12 +7,13 @@ #include #include #include -#include #include "Common/Analytics.h" -#include "Common/CommonTypes.h" #include "Common/Config/Config.h" +#include "Core/PerformanceSample.h" +#include "Core/PerformanceSampleAggregator.h" + #if defined(ANDROID) #include #endif @@ -138,14 +139,12 @@ public: // Get the base builder for building a report const Common::AnalyticsReportBuilder& BaseBuilder() const { return m_base_builder; } - 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. + // Performance reports are generated using data from 100 consecutive frames. + // Report starting times are randomized to obtain a wider range of sample data. + // The first report begins 5-8 minutes after a game is launched. + // Successive reports begin 30-33 minutes after the previous report finishes. // // This method is NOT thread-safe. void ReportPerformanceInfo(PerformanceSample sample); @@ -173,21 +172,7 @@ 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; + PerformanceSampleAggregator m_sample_aggregator; // What quirks have already been reported about the current game. std::array(GameQuirk::Count)> m_reported_quirks; diff --git a/Source/Core/Core/PerformanceSample.h b/Source/Core/Core/PerformanceSample.h new file mode 100644 index 0000000000..34e289408a --- /dev/null +++ b/Source/Core/Core/PerformanceSample.h @@ -0,0 +1,12 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +struct PerformanceSample +{ + double speed_ratio; // See SystemTimers::GetEstimatedEmulationPerformance(). + int num_prims; + int num_draw_calls; +}; diff --git a/Source/Core/Core/PerformanceSampleAggregator.cpp b/Source/Core/Core/PerformanceSampleAggregator.cpp new file mode 100644 index 0000000000..4328f066e4 --- /dev/null +++ b/Source/Core/Core/PerformanceSampleAggregator.cpp @@ -0,0 +1,105 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Core/PerformanceSampleAggregator.h" + +#include + +#include "Common/Random.h" + +namespace +{ +constexpr size_t s_samples_per_report = 100; + +std::chrono::microseconds GetCurrentMicroseconds() +{ + const std::chrono::high_resolution_clock::duration time_since_epoch = + std::chrono::high_resolution_clock::now().time_since_epoch(); + return std::chrono::duration_cast(time_since_epoch); +} + +std::chrono::microseconds GetSamplingStartTimeJitter() +{ + constexpr long long max_delay = std::chrono::microseconds(std::chrono::minutes(3)).count(); + return std::chrono::microseconds(Common::Random::GenerateValue() % max_delay); +} + +std::chrono::microseconds +GetSamplingStartTimestampUsingBaseDelay(const std::chrono::microseconds base_delay) +{ + const std::chrono::microseconds now = GetCurrentMicroseconds(); + const std::chrono::microseconds jitter = GetSamplingStartTimeJitter(); + const std::chrono::microseconds sampling_start_timestamp = now + base_delay + jitter; + return sampling_start_timestamp; +} + +std::chrono::microseconds GetInitialSamplingStartTimestamp() +{ + constexpr std::chrono::microseconds base_initial_delay = std::chrono::minutes(5); + return GetSamplingStartTimestampUsingBaseDelay(base_initial_delay); +} + +std::chrono::microseconds GetRepeatSamplingStartTimestamp() +{ + constexpr std::chrono::microseconds base_repeat_delay = std::chrono::minutes(30); + return GetSamplingStartTimestampUsingBaseDelay(base_repeat_delay); +} + +PerformanceSampleAggregator::CompletedReport +GetCompletedReport(const std::vector& samples) +{ + PerformanceSampleAggregator::CompletedReport report; + const size_t num_samples = samples.size(); + report.speed.resize(num_samples); + report.primitives.resize(num_samples); + report.draw_calls.resize(num_samples); + + for (size_t i = 0; i < num_samples; ++i) + { + const PerformanceSample& sample = samples[i]; + report.speed[i] = static_cast(sample.speed_ratio * 1'000); + report.primitives[i] = sample.num_prims; + report.draw_calls[i] = sample.num_draw_calls; + } + return report; +} +} // namespace + +PerformanceSampleAggregator::PerformanceSampleAggregator() + : m_samples(std::vector(s_samples_per_report)), + m_next_starting_timestamp(std::numeric_limits::max()) +{ +} + +void PerformanceSampleAggregator::InitializePerformanceSampling() +{ + m_samples.clear(); + m_next_starting_timestamp = GetInitialSamplingStartTimestamp(); +} + +void PerformanceSampleAggregator::ResetPerformanceSampling() +{ + m_samples.clear(); + m_next_starting_timestamp = GetRepeatSamplingStartTimestamp(); +} + +void PerformanceSampleAggregator::AddSampleIfSamplingInProgress(PerformanceSample&& sample) +{ + if (GetCurrentMicroseconds() >= m_next_starting_timestamp) + { + m_samples.push_back(sample); + } +} + +std::optional +PerformanceSampleAggregator::PopReportIfComplete() +{ + if (m_samples.size() < s_samples_per_report) + { + return std::nullopt; + } + const CompletedReport report = GetCompletedReport(m_samples); + ResetPerformanceSampling(); + return report; +} diff --git a/Source/Core/Core/PerformanceSampleAggregator.h b/Source/Core/Core/PerformanceSampleAggregator.h new file mode 100644 index 0000000000..27354ff2c6 --- /dev/null +++ b/Source/Core/Core/PerformanceSampleAggregator.h @@ -0,0 +1,38 @@ +// Copyright 2021 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" + +#include "Core/PerformanceSample.h" + +class PerformanceSampleAggregator +{ +public: + PerformanceSampleAggregator(); + + struct CompletedReport + { + std::vector speed; + std::vector primitives; + std::vector draw_calls; + }; + + // Called on game start / title switch. + void InitializePerformanceSampling(); + void AddSampleIfSamplingInProgress(PerformanceSample&& sample); + std::optional PopReportIfComplete(); + +private: + // Called after sampling report is completed + void ResetPerformanceSampling(); + + std::vector m_samples; + std::chrono::microseconds m_next_starting_timestamp; +}; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 9959caf60c..c1e0c663fd 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -459,6 +459,8 @@ + + @@ -1164,6 +1166,7 @@ +