mirror of
				https://github.com/dolphin-emu/dolphin.git
				synced 2025-10-25 17:39:09 +00:00 
			
		
		
		
	SPDX standardizes how source code conveys its copyright and licensing information. See https://spdx.github.io/spdx-spec/1-rationale/ . SPDX tags are adopted in many large projects, including things like the Linux kernel.
		
			
				
	
	
		
			192 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2016 Dolphin Emulator Project
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <chrono>
 | |
| #include <memory>
 | |
| #include <shared_mutex>
 | |
| #include <string>
 | |
| #include <string_view>
 | |
| #include <thread>
 | |
| #include <utility>
 | |
| #include <vector>
 | |
| 
 | |
| #include "Common/CommonTypes.h"
 | |
| #include "Common/Event.h"
 | |
| #include "Common/Flag.h"
 | |
| #include "Common/HttpRequest.h"
 | |
| #include "Common/SPSCQueue.h"
 | |
| 
 | |
| // 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<AnalyticsReporter>();
 | |
| // if (user_gave_consent)
 | |
| // {
 | |
| //     s_reporter->SetBackend(std::make_unique<MyReportingBackend>());
 | |
| // }
 | |
| // 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/DolphinAnalytics.h.
 | |
| class AnalyticsReportingBackend
 | |
| {
 | |
| public:
 | |
|   virtual ~AnalyticsReportingBackend() = default;
 | |
|   // 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) : m_report{other.Get()} {}
 | |
|   AnalyticsReportBuilder(AnalyticsReportBuilder&& other) : m_report{other.Consume()} {}
 | |
| 
 | |
|   const AnalyticsReportBuilder& operator=(const AnalyticsReportBuilder& other)
 | |
|   {
 | |
|     if (this != &other)
 | |
|     {
 | |
|       std::string other_report = other.Get();
 | |
|       std::lock_guard lk{m_lock};
 | |
|       m_report = std::move(other_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 <typename T>
 | |
|   AnalyticsReportBuilder& AddData(std::string_view key, const T& value)
 | |
|   {
 | |
|     std::lock_guard lk{m_lock};
 | |
|     AppendSerializedValue(&m_report, key);
 | |
|     AppendSerializedValue(&m_report, value);
 | |
|     return *this;
 | |
|   }
 | |
| 
 | |
|   template <typename T>
 | |
|   AnalyticsReportBuilder& AddData(std::string_view key, const std::vector<T>& value)
 | |
|   {
 | |
|     std::lock_guard lk{m_lock};
 | |
|     AppendSerializedValue(&m_report, key);
 | |
|     AppendSerializedValueVector(&m_report, value);
 | |
|     return *this;
 | |
|   }
 | |
| 
 | |
|   std::string Get() const
 | |
|   {
 | |
|     std::shared_lock 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, std::string_view 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);
 | |
| 
 | |
|   static void AppendSerializedValueVector(std::string* report, const std::vector<u32>& v);
 | |
| 
 | |
|   mutable std::shared_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<AnalyticsReportingBackend> 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<AnalyticsReportingBackend> m_backend;
 | |
|   AnalyticsReportBuilder m_base_builder;
 | |
| 
 | |
|   std::thread m_reporter_thread;
 | |
|   Common::Event m_reporter_event;
 | |
|   Common::Flag m_reporter_stop_request;
 | |
|   SPSCQueue<std::string> 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:
 | |
|   explicit HttpAnalyticsBackend(std::string endpoint);
 | |
|   ~HttpAnalyticsBackend() override;
 | |
| 
 | |
|   void Send(std::string report) override;
 | |
| 
 | |
| protected:
 | |
|   std::string m_endpoint;
 | |
|   HttpRequest m_http{std::chrono::seconds{5}};
 | |
| };
 | |
| }  // namespace Common
 |