diff --git a/Source/Core/Common/Analytics.cpp b/Source/Core/Common/Analytics.cpp new file mode 100644 index 0000000000..324748936d --- /dev/null +++ b/Source/Core/Common/Analytics.cpp @@ -0,0 +1,231 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include + +#include "Common/Analytics.h" +#include "Common/CommonTypes.h" +#include "Common/StringUtil.h" + +namespace Common +{ +namespace +{ +// Format version number, used as the first byte of every report sent. +// Increment for any change to the wire format. +constexpr u8 WIRE_FORMAT_VERSION = 0; + +// Identifiers for the value types supported by the analytics reporting wire +// format. +enum class TypeId : u8 +{ + STRING = 0, + BOOL = 1, + UINT = 2, + SINT = 3, + FLOAT = 4, +}; + +void AppendBool(std::string* out, bool v) +{ + out->push_back(v ? '\xFF' : '\x00'); +} + +void AppendVarInt(std::string* out, u64 v) +{ + do + { + u8 current_byte = v & 0x7F; + v >>= 7; + current_byte |= (!!v) << 7; + out->push_back(current_byte); + } + while (v); +} + +void AppendBytes(std::string* out, const u8* bytes, u32 length, + bool encode_length = true) +{ + if (encode_length) + { + AppendVarInt(out, length); + } + out->append(reinterpret_cast(bytes), length); +} + +void AppendType(std::string* out, TypeId type) +{ + out->push_back(static_cast(type)); +} + +// Dummy write function for curl. +size_t DummyCurlWriteFunction(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + return size * nmemb; +} + +} // namespace + +AnalyticsReportBuilder::AnalyticsReportBuilder() +{ + m_report.push_back(WIRE_FORMAT_VERSION); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + const std::string& v) +{ + AppendType(report, TypeId::STRING); + AppendBytes(report, reinterpret_cast(v.data()), static_cast(v.size())); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + const char* v) +{ + AppendSerializedValue(report, std::string(v)); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + bool v) +{ + AppendType(report, TypeId::BOOL); + AppendBool(report, v); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + u64 v) +{ + AppendType(report, TypeId::UINT); + AppendVarInt(report, v); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + s64 v) +{ + AppendType(report, TypeId::SINT); + AppendBool(report, v >= 0); + AppendVarInt(report, static_cast(std::abs(v))); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + u32 v) +{ + AppendSerializedValue(report, static_cast(v)); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + s32 v) +{ + AppendSerializedValue(report, static_cast(v)); +} + +void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, + float v) +{ + AppendType(report, TypeId::FLOAT); + AppendBytes(report, reinterpret_cast(&v), sizeof (v), false); +} + +AnalyticsReporter::AnalyticsReporter() +{ + m_reporter_thread = std::thread(&AnalyticsReporter::ThreadProc, this); +} + +AnalyticsReporter::~AnalyticsReporter() +{ + // Set the exit request flag and wait for the thread to honor it. + m_reporter_stop_request.Set(); + m_reporter_event.Set(); + m_reporter_thread.join(); +} + +void AnalyticsReporter::Send(AnalyticsReportBuilder&& report) +{ + // Put a bound on the size of the queue to avoid uncontrolled memory growth. + constexpr u32 QUEUE_SIZE_LIMIT = 25; + if (m_reports_queue.Size() < QUEUE_SIZE_LIMIT) + { + m_reports_queue.Push(report.Consume()); + m_reporter_event.Set(); + } +} + +void AnalyticsReporter::ThreadProc() +{ + while (true) + { + m_reporter_event.Wait(); + if (m_reporter_stop_request.IsSet()) + { + return; + } + + while (!m_reports_queue.Empty()) + { + std::shared_ptr backend(m_backend); + + if (backend) + { + std::string report; + m_reports_queue.Pop(report); + backend->Send(std::move(report)); + } + else + { + break; + } + + // Recheck after each report sent. + if (m_reporter_stop_request.IsSet()) + { + return; + } + } + } +} + +void StdoutAnalyticsBackend::Send(std::string report) +{ + printf("Analytics report sent:\n%s", HexDump( + reinterpret_cast(report.data()), report.size()).c_str()); +} + +HttpAnalyticsBackend::HttpAnalyticsBackend(const std::string& endpoint) +{ + CURL* curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, true); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &DummyCurlWriteFunction); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 3000); + + // ALPN support is enabled by default but requires Windows >= 8.1. + curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false); + + m_curl = curl; + } +} + +HttpAnalyticsBackend::~HttpAnalyticsBackend() +{ + if (m_curl) + { + curl_easy_cleanup(m_curl); + } +} + +void HttpAnalyticsBackend::Send(std::string report) +{ + if (!m_curl) + return; + + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, report.c_str()); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, report.size()); + curl_easy_perform(m_curl); +} + +} // namespace Common diff --git a/Source/Core/Common/Analytics.h b/Source/Core/Common/Analytics.h new file mode 100644 index 0000000000..608496cc3f --- /dev/null +++ b/Source/Core/Common/Analytics.h @@ -0,0 +1,194 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Event.h" +#include "Common/FifoQueue.h" +#include "Common/Flag.h" + +typedef void CURL; + +// Utilities for analytics reporting in Dolphin. This reporting is designed to +// provide anonymous data about how well Dolphin performs in the wild. It also +// allows developers to declare trace points in Dolphin's source code and get +// information about what games trigger these conditions. +// +// This unfortunately implements Yet Another Serialization Framework within +// Dolphin. We cannot really use ChunkFile because there is precedents for +// backwards incompatible changes in the ChunkFile format. We could use +// something like protobuf but setting up external dependencies is Hardâ„¢. +// +// Example usage: +// +// static auto s_reporter = std::make_unique(); +// if (user_gave_consent) +// { +// s_reporter->SetBackend(std::make_unique()); +// } +// s_reporter->Send(s_reporter->Builder() +// .AddData("my_key", 42) +// .AddData("other_key", false)); + +namespace Common +{ + +// Generic interface for an analytics reporting backends. The main +// implementation used in Dolphin can be found in Core/Analytics.h. +class AnalyticsReportingBackend +{ +public: + virtual ~AnalyticsReportingBackend() {} + + // Called from the AnalyticsReporter backend thread. + virtual void Send(std::string report) = 0; +}; + +// Builder object for an analytics report. +class AnalyticsReportBuilder +{ +public: + AnalyticsReportBuilder(); + ~AnalyticsReportBuilder() = default; + + AnalyticsReportBuilder(const AnalyticsReportBuilder& other) + { + *this = other; + } + + AnalyticsReportBuilder(AnalyticsReportBuilder&& other) + { + std::lock_guard lk(other.m_lock); + m_report = std::move(other.m_report); + } + + const AnalyticsReportBuilder& operator=(const AnalyticsReportBuilder& other) + { + if (this != &other) + { + std::lock_guard lk(m_lock); + std::lock_guard lk2(other.m_lock); + m_report = other.m_report; + } + return *this; + } + + // Append another builder to this one. + AnalyticsReportBuilder& AddBuilder(const AnalyticsReportBuilder& other) + { + // Get before locking the object to avoid deadlocks with this += this. + std::string other_report = other.Get(); + std::lock_guard lk(m_lock); + m_report += other_report; + return *this; + } + + template + AnalyticsReportBuilder& AddData(const std::string& key, const T& value) + { + std::lock_guard lk(m_lock); + AppendSerializedValue(&m_report, key); + AppendSerializedValue(&m_report, value); + return *this; + } + + std::string Get() const + { + std::lock_guard lk(m_lock); + return m_report; + } + + // More efficient version of Get(). + std::string Consume() + { + std::lock_guard lk(m_lock); + return std::move(m_report); + } + +protected: + static void AppendSerializedValue(std::string* report, const std::string& v); + static void AppendSerializedValue(std::string* report, const char* v); + static void AppendSerializedValue(std::string* report, bool v); + static void AppendSerializedValue(std::string* report, u64 v); + static void AppendSerializedValue(std::string* report, s64 v); + static void AppendSerializedValue(std::string* report, u32 v); + static void AppendSerializedValue(std::string* report, s32 v); + static void AppendSerializedValue(std::string* report, float v); + + // Should really be a std::shared_mutex, unfortunately that's C++17 only. + mutable std::mutex m_lock; + std::string m_report; +}; + +class AnalyticsReporter +{ +public: + AnalyticsReporter(); + ~AnalyticsReporter(); + + // Sets a reporting backend and enables sending reports. Do not set a remote + // backend without user consent. + void SetBackend(std::unique_ptr backend) + { + m_backend = std::move(backend); + m_reporter_event.Set(); // In case reports are waiting queued. + } + + // Gets the base report builder which is closed for each subsequent report + // being sent. DO NOT use this builder to send a report. Only use it to add + // new fields that should be globally available. + AnalyticsReportBuilder& BaseBuilder() { return m_base_builder; } + + // Gets a cloned builder that can be used to send a report. + AnalyticsReportBuilder Builder() const { return m_base_builder; } + + // Enqueues a report for sending. Consumes the report builder. + void Send(AnalyticsReportBuilder&& report); + + // For convenience. + void Send(AnalyticsReportBuilder& report) { Send(std::move(report)); } + +protected: + void ThreadProc(); + + std::shared_ptr m_backend; + AnalyticsReportBuilder m_base_builder; + + std::thread m_reporter_thread; + Common::Event m_reporter_event; + Common::Flag m_reporter_stop_request; + FifoQueue m_reports_queue; +}; + +// Analytics backend to be used for debugging purpose, which dumps reports to +// stdout. +class StdoutAnalyticsBackend : public AnalyticsReportingBackend +{ +public: + void Send(std::string report) override; +}; + +// Analytics backend that POSTs data to a remote HTTP(s) endpoint. WARNING: +// remember to get explicit user consent before using. +class HttpAnalyticsBackend : public AnalyticsReportingBackend +{ +public: + HttpAnalyticsBackend(const std::string& endpoint); + ~HttpAnalyticsBackend() override; + + void Send(std::string report) override; + +protected: + CURL* m_curl = nullptr; +}; + +} // namespace Common diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index eae710af45..d1ec894a70 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -1,4 +1,5 @@ -set(SRCS BreakPoints.cpp +set(SRCS Analytics.cpp + BreakPoints.cpp CDUtils.cpp ColorUtil.cpp ENetUtil.cpp @@ -42,7 +43,7 @@ else() Logging/ConsoleListenerNix.cpp) endif() -list(APPEND LIBS enet) +list(APPEND LIBS enet ${CURL_LIBRARIES}) if(_M_ARM_64) set(SRCS ${SRCS} Arm64Emitter.cpp diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index d6541d1b31..101b082f0e 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -35,6 +35,7 @@ + @@ -141,6 +142,7 @@ + @@ -194,6 +196,9 @@ {bdb6578b-0691-4e80-a46c-df21639fd3b8} + + {bb00605c-125f-4a21-b33b-7bf418322dcb} + {41279555-f94f-4ebc-99de-af863c10c5c4} @@ -204,4 +209,4 @@ - + \ No newline at end of file diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 28b01541a4..6bb69f6a17 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -224,6 +224,7 @@ + @@ -284,6 +285,7 @@ GL\GLInterface + @@ -291,4 +293,4 @@ - + \ No newline at end of file diff --git a/Source/Core/Core/Analytics.cpp b/Source/Core/Core/Analytics.cpp new file mode 100644 index 0000000000..939992cf04 --- /dev/null +++ b/Source/Core/Core/Analytics.cpp @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#elif defined(__APPLE__) +#include +#endif + +#include "Common/Analytics.h" +#include "Common/Common.h" +#include "Common/CommonTypes.h" +#include "Common/CPUDetect.h" +#include "Common/StringUtil.h" +#include "Core/Analytics.h" +#include "Core/ConfigManager.h" +#include "Core/Movie.h" +#include "Core/NetPlayProto.h" +#include "VideoCommon/VideoConfig.h" + +namespace +{ +constexpr const char* ANALYTICS_ENDPOINT = "https://analytics.dolphin-emu.org/report"; +} // namespace + +std::mutex DolphinAnalytics::s_instance_mutex; +std::shared_ptr DolphinAnalytics::s_instance; + +DolphinAnalytics::DolphinAnalytics() +{ + ReloadConfig(); + MakeBaseBuilder(); +} + +std::shared_ptr DolphinAnalytics::Instance() +{ + std::lock_guard lk(s_instance_mutex); + if (!s_instance) + { + s_instance.reset(new DolphinAnalytics()); + } + return s_instance; +} + +void DolphinAnalytics::ReloadConfig() +{ + std::lock_guard lk(m_reporter_mutex); + + // Install the HTTP backend if analytics support is enabled. + std::unique_ptr new_backend; + if (SConfig::GetInstance().m_analytics_enabled) + { + new_backend = std::make_unique(ANALYTICS_ENDPOINT); + } + m_reporter.SetBackend(std::move(new_backend)); + + // Load the unique ID or generate it if needed. + m_unique_id = SConfig::GetInstance().m_analytics_id; + if (m_unique_id.empty()) + { + GenerateNewIdentity(); + } +} + +void DolphinAnalytics::GenerateNewIdentity() +{ + std::random_device rd; + u64 id_high = (static_cast(rd()) << 32) | rd(); + u64 id_low = (static_cast(rd()) << 32) | rd(); + m_unique_id = StringFromFormat("%016" PRIx64 "%016" PRIx64, id_high, id_low); + + // Save the new id in the configuration. + SConfig::GetInstance().m_analytics_id = m_unique_id; + SConfig::GetInstance().SaveSettings(); +} + +std::string DolphinAnalytics::MakeUniqueId(const std::string& data) +{ + u8 digest[20]; + std::string input = m_unique_id + data; + mbedtls_sha1(reinterpret_cast(input.c_str()), input.size(), + digest); + + // Convert to hex string and truncate to 64 bits. + std::string out; + for (int i = 0; i < 8; ++i) + { + out += StringFromFormat("%02hhx", digest[i]); + } + return out; +} + +void DolphinAnalytics::ReportDolphinStart(const std::string& ui_type) +{ + Common::AnalyticsReportBuilder builder(m_base_builder); + builder.AddData("type", "dolphin-start"); + builder.AddData("ui-type", ui_type); + builder.AddData("id", MakeUniqueId("dolphin-start")); + Send(builder); +} + +void DolphinAnalytics::ReportGameStart() +{ + MakePerGameBuilder(); + + Common::AnalyticsReportBuilder builder(m_per_game_builder); + builder.AddData("type", "game-start"); + Send(builder); +} + +void DolphinAnalytics::MakeBaseBuilder() +{ + Common::AnalyticsReportBuilder builder; + + // Version information. + builder.AddData("version-desc", scm_desc_str); + builder.AddData("version-hash", scm_rev_git_str); + builder.AddData("version-branch", scm_branch_str); + builder.AddData("version-dist", scm_distributor_str); + + // CPU information. + builder.AddData("cpu-summary", cpu_info.Summarize()); + + // OS information. +#if defined(_WIN32) + builder.AddData("os-type", "windows"); + + // Windows 8 removes support for GetVersionEx and such. Stupid. + DWORD (WINAPI *RtlGetVersion)(LPOSVERSIONINFOEXW); + *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandle(TEXT("ntdll")), "RtlGetVersion"); + + OSVERSIONINFOEXW winver; + winver.dwOSVersionInfoSize = sizeof(winver); + if (RtlGetVersion != nullptr) + { + RtlGetVersion(&winver); + 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)); + builder.AddData("win-ver-spmajor", static_cast(winver.wServicePackMajor)); + builder.AddData("win-ver-spminor", static_cast(winver.wServicePackMinor)); + } +#elif defined(ANDROID) + builder.AddData("os-type", "android"); +#elif defined(__APPLE__) + builder.AddData("os-type", "osx"); + + SInt32 osxmajor, osxminor, osxbugfix; + Gestalt(gestaltSystemVersionMajor, &osxmajor); + Gestalt(gestaltSystemVersionMinor, &osxminor); + Gestalt(gestaltSystemVersionBugFix, &osxbugfix); + + builder.AddData("osx-ver-major", osxmajor); + builder.AddData("osx-ver-minor", osxminor); + builder.AddData("osx-ver-bugfix", osxbugfix); +#elif defined(__linux__) + builder.AddData("os-type", "linux"); +#elif defined(__FreeBSD__) + builder.AddData("os-type", "freebsd"); +#else + builder.AddData("os-type", "unknown"); +#endif + + m_base_builder = builder; +} + +void DolphinAnalytics::MakePerGameBuilder() +{ + Common::AnalyticsReportBuilder builder(m_base_builder); + + // Gameid. + builder.AddData("gameid", SConfig::GetInstance().GetUniqueID()); + + // Unique id bound to the gameid. + builder.AddData("id", MakeUniqueId(SConfig::GetInstance().GetUniqueID())); + + // Configuration. + builder.AddData("cfg-dsp-hle", SConfig::GetInstance().bDSPHLE); + builder.AddData("cfg-dsp-jit", SConfig::GetInstance().m_DSPEnableJIT); + builder.AddData("cfg-dsp-thread", SConfig::GetInstance().bDSPThread); + builder.AddData("cfg-cpu-thread", SConfig::GetInstance().bCPUThread); + builder.AddData("cfg-idle-skip", SConfig::GetInstance().bSkipIdle); + builder.AddData("cfg-fastmem", SConfig::GetInstance().bFastmem); + builder.AddData("cfg-syncgpu", SConfig::GetInstance().bSyncGPU); + builder.AddData("cfg-audio-backend", SConfig::GetInstance().sBackend); + builder.AddData("cfg-video-backend", SConfig::GetInstance().m_strVideoBackend); + builder.AddData("cfg-oc-enable", SConfig::GetInstance().m_OCEnable); + builder.AddData("cfg-oc-factor", SConfig::GetInstance().m_OCFactor); + builder.AddData("cfg-render-to-main", SConfig::GetInstance().bRenderToMain); + + // Video configuration. + builder.AddData("cfg-gfx-multisamples", g_Config.iMultisamples); + builder.AddData("cfg-gfx-ssaa", g_Config.bSSAA); + builder.AddData("cfg-gfx-anisotropy", g_Config.iMaxAnisotropy); + builder.AddData("cfg-gfx-realxfb", g_Config.RealXFBEnabled()); + builder.AddData("cfg-gfx-virtualxfb", g_Config.VirtualXFBEnabled()); + builder.AddData("cfg-gfx-vsync", g_Config.bVSync); + builder.AddData("cfg-gfx-fullscreen", g_Config.bFullscreen); + builder.AddData("cfg-gfx-exclusive-mode", g_Config.bExclusiveMode); + builder.AddData("cfg-gfx-aspect-ratio", g_Config.iAspectRatio); + builder.AddData("cfg-gfx-efb-access", g_Config.bEFBAccessEnable); + builder.AddData("cfg-gfx-efb-scale", g_Config.iEFBScale); + builder.AddData("cfg-gfx-efb-copy-format-changes", g_Config.bEFBEmulateFormatChanges); + builder.AddData("cfg-gfx-efb-copy-ram", !g_Config.bSkipEFBCopyToRam); + builder.AddData("cfg-gfx-efb-copy-scaled", g_Config.bCopyEFBScaled); + builder.AddData("cfg-gfx-tc-samples", g_Config.iSafeTextureCache_ColorSamples); + builder.AddData("cfg-gfx-stereo-mode", g_Config.iStereoMode); + + // GPU features. + if (g_Config.iAdapter < static_cast(g_Config.backend_info.Adapters.size())) + { + builder.AddData("gpu-adapter", g_Config.backend_info.Adapters[g_Config.iAdapter]); + } + builder.AddData("gpu-has-exclusive-fullscreen", g_Config.backend_info.bSupportsExclusiveFullscreen); + builder.AddData("gpu-has-dual-source-blend", g_Config.backend_info.bSupportsDualSourceBlend); + builder.AddData("gpu-has-primitive-restart", g_Config.backend_info.bSupportsPrimitiveRestart); + builder.AddData("gpu-has-oversized-viewports", g_Config.backend_info.bSupportsOversizedViewports); + builder.AddData("gpu-has-geometry-shaders", g_Config.backend_info.bSupportsGeometryShaders); + builder.AddData("gpu-has-3d-vision", g_Config.backend_info.bSupports3DVision); + builder.AddData("gpu-has-early-z", g_Config.backend_info.bSupportsEarlyZ); + builder.AddData("gpu-has-binding-layout", g_Config.backend_info.bSupportsBindingLayout); + builder.AddData("gpu-has-bbox", g_Config.backend_info.bSupportsBBox); + builder.AddData("gpu-has-gs-instancing", g_Config.backend_info.bSupportsGSInstancing); + builder.AddData("gpu-has-post-processing", g_Config.backend_info.bSupportsPostProcessing); + builder.AddData("gpu-has-palette-conversion", g_Config.backend_info.bSupportsPaletteConversion); + builder.AddData("gpu-has-clip-control", g_Config.backend_info.bSupportsClipControl); + builder.AddData("gpu-has-ssaa", g_Config.backend_info.bSupportsSSAA); + + // NetPlay / recording. + builder.AddData("netplay", NetPlay::IsNetPlayRunning()); + builder.AddData("movie", Movie::IsMovieActive()); + + m_per_game_builder = builder; +} diff --git a/Source/Core/Core/Analytics.h b/Source/Core/Core/Analytics.h new file mode 100644 index 0000000000..bf21224c4f --- /dev/null +++ b/Source/Core/Core/Analytics.h @@ -0,0 +1,74 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/Analytics.h" + +// Non generic part of the Dolphin Analytics framework. See Common/Analytics.h +// for the main documentation. + +class DolphinAnalytics +{ +public: + // Performs lazy-initialization of a singleton and returns the instance. + static std::shared_ptr Instance(); + + // Resets and recreates the analytics system in order to reload + // configuration. + void ReloadConfig(); + + // Rotates the unique identifier used for this instance of Dolphin and saves + // it into the configuration. + void GenerateNewIdentity(); + + // Reports a Dolphin start event. + void ReportDolphinStart(const std::string& ui_type); + + // Generates a base report for a "Game start" event. Also preseeds the + // per-game base data. + void ReportGameStart(); + + // Forward Send method calls to the reporter. + template + void Send(T report) + { + std::lock_guard lk(m_reporter_mutex); + m_reporter.Send(report); + } + +private: + DolphinAnalytics(); + + void MakeBaseBuilder(); + void MakePerGameBuilder(); + + // Returns a unique ID derived on the global unique ID, hashed with some + // report-specific data. This avoid correlation between different types of + // events. + std::string MakeUniqueId(const std::string& data); + + // Unique ID. This should never leave the application. Only used derived + // values created by MakeUniqueId. + std::string m_unique_id; + + // Builder that contains all non variable data that should be sent with all + // reports. + Common::AnalyticsReportBuilder m_base_builder; + + // Builder that contains per game data and is initialized when a game start + // report is sent. + Common::AnalyticsReportBuilder m_per_game_builder; + + std::mutex m_reporter_mutex; + Common::AnalyticsReporter m_reporter; + + // Shared pointer in order to allow for multithreaded use of the instance and + // avoid races at reinitialization time. + static std::mutex s_instance_mutex; + static std::shared_ptr s_instance; +}; diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 4fe1353d23..f08080a3e2 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -1,4 +1,5 @@ set(SRCS ActionReplay.cpp + Analytics.cpp ARDecrypt.cpp BootManager.cpp ConfigManager.cpp diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index ae363bb890..cfa9e4b0b9 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -55,6 +55,7 @@ SConfig::SConfig() bProgressive(false), bPAL60(false), bDisableScreenSaver(false), iPosX(100), iPosY(100), iWidth(800), iHeight(600), + m_analytics_enabled(false), m_analytics_permission_asked(false), bLoopFifoReplay(true) { LoadDefaults(); @@ -95,6 +96,7 @@ void SConfig::SaveSettings() SaveDSPSettings(ini); SaveInputSettings(ini); SaveFifoPlayerSettings(ini); + SaveAnalyticsSettings(ini); ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); m_SYSCONF->Save(); @@ -309,6 +311,15 @@ void SConfig::SaveFifoPlayerSettings(IniFile& ini) fifoplayer->Set("LoopReplay", bLoopFifoReplay); } +void SConfig::SaveAnalyticsSettings(IniFile& ini) +{ + IniFile::Section* analytics = ini.GetOrCreateSection("Analytics"); + + analytics->Set("ID", m_analytics_id); + analytics->Set("Enabled", m_analytics_enabled); + analytics->Set("PermissionAsked", m_analytics_permission_asked); +} + void SConfig::LoadSettings() { INFO_LOG(BOOT, "Loading Settings from %s", File::GetUserPath(F_DOLPHINCONFIG_IDX).c_str()); @@ -324,6 +335,7 @@ void SConfig::LoadSettings() LoadDSPSettings(ini); LoadInputSettings(ini); LoadFifoPlayerSettings(ini); + LoadAnalyticsSettings(ini); m_SYSCONF = new SysConf(); } @@ -586,6 +598,15 @@ void SConfig::LoadFifoPlayerSettings(IniFile& ini) fifoplayer->Get("LoopReplay", &bLoopFifoReplay, true); } +void SConfig::LoadAnalyticsSettings(IniFile& ini) +{ + IniFile::Section* analytics = ini.GetOrCreateSection("Analytics"); + + analytics->Get("ID", &m_analytics_id, ""); + analytics->Get("Enabled", &m_analytics_enabled, false); + analytics->Get("PermissionAsked", &m_analytics_permission_asked, false); +} + void SConfig::LoadDefaults() { bEnableDebugging = false; @@ -626,6 +647,10 @@ void SConfig::LoadDefaults() iWidth = 800; iHeight = 600; + m_analytics_id = ""; + m_analytics_enabled = false; + m_analytics_permission_asked = false; + bLoopFifoReplay = true; bJITOff = false; // debugger only settings diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 6971f5de79..a2cc3c507d 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -127,6 +127,11 @@ struct SConfig : NonCopyable int iPosX, iPosY, iWidth, iHeight; + // Analytics settings. + std::string m_analytics_id; + bool m_analytics_enabled; + bool m_analytics_permission_asked; + // Fifo Player related settings bool bLoopFifoReplay; @@ -294,6 +299,7 @@ private: void SaveInputSettings(IniFile& ini); void SaveMovieSettings(IniFile& ini); void SaveFifoPlayerSettings(IniFile& ini); + void SaveAnalyticsSettings(IniFile& ini); void LoadGeneralSettings(IniFile& ini); void LoadInterfaceSettings(IniFile& ini); @@ -304,6 +310,7 @@ private: void LoadInputSettings(IniFile& ini); void LoadMovieSettings(IniFile& ini); void LoadFifoPlayerSettings(IniFile& ini); + void LoadAnalyticsSettings(IniFile& ini); static SConfig* m_Instance; }; diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index ab888de322..ab108c77d2 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -25,6 +25,7 @@ #include "Common/Timer.h" #include "Common/Logging/LogManager.h" +#include "Core/Analytics.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" @@ -355,6 +356,9 @@ static void CpuThread() g_video_backend->Video_Prepare(); } + // This needs to be delayed until after the video backend is ready. + DolphinAnalytics::Instance()->ReportGameStart(); + if (_CoreParameter.bFastmem) EMM::InstallExceptionHandler(); // Let's run under memory watch diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 490e724006..e05401d41f 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -36,6 +36,7 @@ + @@ -248,6 +249,7 @@ + @@ -480,4 +482,4 @@ - + \ No newline at end of file diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index 1a3df6a407..504b1b69d5 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -729,6 +729,7 @@ PowerPC\Jit64Common + @@ -1247,8 +1248,9 @@ PowerPC\Jit64Common + - + \ No newline at end of file diff --git a/Source/Core/DolphinWX/Main.cpp b/Source/Core/DolphinWX/Main.cpp index b9c6955045..fb6b8f7a3e 100644 --- a/Source/Core/DolphinWX/Main.cpp +++ b/Source/Core/DolphinWX/Main.cpp @@ -28,6 +28,7 @@ #include "Common/Thread.h" #include "Common/Logging/LogManager.h" +#include "Core/Analytics.h" #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/Host.h" @@ -125,6 +126,8 @@ bool DolphinApp::OnInit() VideoBackendBase::ActivateBackend(SConfig::GetInstance().m_strVideoBackend); + DolphinAnalytics::Instance()->ReportDolphinStart("wx"); + // Enable the PNG image handler for screenshots wxImage::AddHandler(new wxPNGHandler); diff --git a/Source/Core/DolphinWX/MainNoGUI.cpp b/Source/Core/DolphinWX/MainNoGUI.cpp index aaf207ade3..01994fea60 100644 --- a/Source/Core/DolphinWX/MainNoGUI.cpp +++ b/Source/Core/DolphinWX/MainNoGUI.cpp @@ -15,6 +15,7 @@ #include "Common/MsgHandler.h" #include "Common/Logging/LogManager.h" +#include "Core/Analytics.h" #include "Core/BootManager.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -362,6 +363,8 @@ int main(int argc, char* argv[]) platform->Init(); + DolphinAnalytics::Instance()->ReportDolphinStart("nogui"); + if (!BootManager::BootCore(argv[optind])) { fprintf(stderr, "Could not boot %s\n", argv[optind]);