Analytics.h 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright 2016 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #pragma once
  4. #include <chrono>
  5. #include <memory>
  6. #include <shared_mutex>
  7. #include <string>
  8. #include <string_view>
  9. #include <thread>
  10. #include <utility>
  11. #include <vector>
  12. #include "Common/CommonTypes.h"
  13. #include "Common/Event.h"
  14. #include "Common/Flag.h"
  15. #include "Common/HttpRequest.h"
  16. #include "Common/SPSCQueue.h"
  17. // Utilities for analytics reporting in Dolphin. This reporting is designed to
  18. // provide anonymous data about how well Dolphin performs in the wild. It also
  19. // allows developers to declare trace points in Dolphin's source code and get
  20. // information about what games trigger these conditions.
  21. //
  22. // This unfortunately implements Yet Another Serialization Framework within
  23. // Dolphin. We cannot really use ChunkFile because there is precedents for
  24. // backwards incompatible changes in the ChunkFile format. We could use
  25. // something like protobuf but setting up external dependencies is Hard™.
  26. //
  27. // Example usage:
  28. //
  29. // static auto s_reporter = std::make_unique<AnalyticsReporter>();
  30. // if (user_gave_consent)
  31. // {
  32. // s_reporter->SetBackend(std::make_unique<MyReportingBackend>());
  33. // }
  34. // s_reporter->Send(s_reporter->Builder()
  35. // .AddData("my_key", 42)
  36. // .AddData("other_key", false));
  37. namespace Common
  38. {
  39. // Generic interface for an analytics reporting backends. The main
  40. // implementation used in Dolphin can be found in Core/DolphinAnalytics.h.
  41. class AnalyticsReportingBackend
  42. {
  43. public:
  44. virtual ~AnalyticsReportingBackend() = default;
  45. // Called from the AnalyticsReporter backend thread.
  46. virtual void Send(std::string report) = 0;
  47. };
  48. // Builder object for an analytics report.
  49. class AnalyticsReportBuilder
  50. {
  51. public:
  52. AnalyticsReportBuilder();
  53. ~AnalyticsReportBuilder() = default;
  54. AnalyticsReportBuilder(const AnalyticsReportBuilder& other) : m_report{other.Get()} {}
  55. AnalyticsReportBuilder(AnalyticsReportBuilder&& other) : m_report{other.Consume()} {}
  56. const AnalyticsReportBuilder& operator=(const AnalyticsReportBuilder& other)
  57. {
  58. if (this != &other)
  59. {
  60. std::string other_report = other.Get();
  61. std::lock_guard lk{m_lock};
  62. m_report = std::move(other_report);
  63. }
  64. return *this;
  65. }
  66. // Append another builder to this one.
  67. AnalyticsReportBuilder& AddBuilder(const AnalyticsReportBuilder& other)
  68. {
  69. // Get before locking the object to avoid deadlocks with this += this.
  70. std::string other_report = other.Get();
  71. std::lock_guard lk{m_lock};
  72. m_report += other_report;
  73. return *this;
  74. }
  75. template <typename T>
  76. AnalyticsReportBuilder& AddData(std::string_view key, const T& value)
  77. {
  78. std::lock_guard lk{m_lock};
  79. AppendSerializedValue(&m_report, key);
  80. AppendSerializedValue(&m_report, value);
  81. return *this;
  82. }
  83. template <typename T>
  84. AnalyticsReportBuilder& AddData(std::string_view key, const std::vector<T>& value)
  85. {
  86. std::lock_guard lk{m_lock};
  87. AppendSerializedValue(&m_report, key);
  88. AppendSerializedValueVector(&m_report, value);
  89. return *this;
  90. }
  91. std::string Get() const
  92. {
  93. std::shared_lock lk{m_lock};
  94. return m_report;
  95. }
  96. // More efficient version of Get().
  97. std::string Consume()
  98. {
  99. std::lock_guard lk{m_lock};
  100. return std::move(m_report);
  101. }
  102. protected:
  103. static void AppendSerializedValue(std::string* report, std::string_view v);
  104. static void AppendSerializedValue(std::string* report, const char* v);
  105. static void AppendSerializedValue(std::string* report, bool v);
  106. static void AppendSerializedValue(std::string* report, u64 v);
  107. static void AppendSerializedValue(std::string* report, s64 v);
  108. static void AppendSerializedValue(std::string* report, u32 v);
  109. static void AppendSerializedValue(std::string* report, s32 v);
  110. static void AppendSerializedValue(std::string* report, float v);
  111. static void AppendSerializedValueVector(std::string* report, const std::vector<u32>& v);
  112. mutable std::shared_mutex m_lock;
  113. std::string m_report;
  114. };
  115. class AnalyticsReporter
  116. {
  117. public:
  118. AnalyticsReporter();
  119. ~AnalyticsReporter();
  120. // Sets a reporting backend and enables sending reports. Do not set a remote
  121. // backend without user consent.
  122. void SetBackend(std::unique_ptr<AnalyticsReportingBackend> backend)
  123. {
  124. m_backend = std::move(backend);
  125. m_reporter_event.Set(); // In case reports are waiting queued.
  126. }
  127. // Gets the base report builder which is closed for each subsequent report
  128. // being sent. DO NOT use this builder to send a report. Only use it to add
  129. // new fields that should be globally available.
  130. AnalyticsReportBuilder& BaseBuilder() { return m_base_builder; }
  131. // Gets a cloned builder that can be used to send a report.
  132. AnalyticsReportBuilder Builder() const { return m_base_builder; }
  133. // Enqueues a report for sending. Consumes the report builder.
  134. void Send(AnalyticsReportBuilder&& report);
  135. // For convenience.
  136. void Send(AnalyticsReportBuilder& report) { Send(std::move(report)); }
  137. protected:
  138. void ThreadProc();
  139. std::shared_ptr<AnalyticsReportingBackend> m_backend;
  140. AnalyticsReportBuilder m_base_builder;
  141. std::thread m_reporter_thread;
  142. Common::Event m_reporter_event;
  143. Common::Flag m_reporter_stop_request;
  144. SPSCQueue<std::string> m_reports_queue;
  145. };
  146. // Analytics backend to be used for debugging purpose, which dumps reports to
  147. // stdout.
  148. class StdoutAnalyticsBackend : public AnalyticsReportingBackend
  149. {
  150. public:
  151. void Send(std::string report) override;
  152. };
  153. // Analytics backend that POSTs data to a remote HTTP(s) endpoint. WARNING:
  154. // remember to get explicit user consent before using.
  155. class HttpAnalyticsBackend : public AnalyticsReportingBackend
  156. {
  157. public:
  158. explicit HttpAnalyticsBackend(std::string endpoint);
  159. ~HttpAnalyticsBackend() override;
  160. void Send(std::string report) override;
  161. protected:
  162. std::string m_endpoint;
  163. HttpRequest m_http{std::chrono::seconds{5}};
  164. };
  165. } // namespace Common