193 lines
5.8 KiB
C++
193 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
|