committed by
Briar
29 changed files with 84 additions and 1123 deletions
-
2src/common/CMakeLists.txt
-
56src/common/scm_rev.cpp.in
-
6src/common/scm_rev.h
-
3src/common/settings.h
-
119src/common/telemetry.cpp
-
209src/common/telemetry.h
-
2src/core/CMakeLists.txt
-
32src/core/core.cpp
-
7src/core/core.h
-
294src/core/telemetry_session.cpp
-
101src/core/telemetry_session.h
-
16src/video_core/renderer_opengl/renderer_opengl.cpp
-
5src/video_core/renderer_opengl/renderer_opengl.h
-
16src/video_core/renderer_vulkan/renderer_vulkan.cpp
-
8src/video_core/renderer_vulkan/renderer_vulkan.h
-
9src/video_core/video_core.cpp
-
2src/web_service/CMakeLists.txt
-
130src/web_service/telemetry_json.cpp
-
44src/web_service/telemetry_json.h
-
11src/yuzu/compatdb.cpp
-
5src/yuzu/compatdb.h
-
24src/yuzu/configuration/configure_web.cpp
-
1src/yuzu/configuration/configure_web.h
-
50src/yuzu/configuration/configure_web.ui
-
29src/yuzu/main.cpp
-
1src/yuzu/main.h
-
2src/yuzu/uisettings.h
-
4src/yuzu_cmd/yuzu.cpp
-
3tools/clang-format.sh
@ -1,119 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <cstring>
|
|
||||
#include "common/scm_rev.h"
|
|
||||
#include "common/telemetry.h"
|
|
||||
|
|
||||
#ifdef ARCHITECTURE_x86_64
|
|
||||
#include "common/x64/cpu_detect.h"
|
|
||||
#endif
|
|
||||
|
|
||||
namespace Common::Telemetry { |
|
||||
|
|
||||
void FieldCollection::Accept(VisitorInterface& visitor) const { |
|
||||
for (const auto& field : fields) { |
|
||||
field.second->Accept(visitor); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void FieldCollection::AddField(std::unique_ptr<FieldInterface> field) { |
|
||||
fields[field->GetName()] = std::move(field); |
|
||||
} |
|
||||
|
|
||||
template <class T> |
|
||||
void Field<T>::Accept(VisitorInterface& visitor) const { |
|
||||
visitor.Visit(*this); |
|
||||
} |
|
||||
|
|
||||
template class Field<bool>; |
|
||||
template class Field<double>; |
|
||||
template class Field<float>; |
|
||||
template class Field<u8>; |
|
||||
template class Field<u16>; |
|
||||
template class Field<u32>; |
|
||||
template class Field<u64>; |
|
||||
template class Field<s8>; |
|
||||
template class Field<s16>; |
|
||||
template class Field<s32>; |
|
||||
template class Field<s64>; |
|
||||
template class Field<std::string>; |
|
||||
template class Field<const char*>; |
|
||||
template class Field<std::chrono::microseconds>; |
|
||||
|
|
||||
void AppendBuildInfo(FieldCollection& fc) { |
|
||||
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; |
|
||||
fc.AddField(FieldType::App, "Git_IsDirty", is_git_dirty); |
|
||||
fc.AddField(FieldType::App, "Git_Branch", Common::g_scm_branch); |
|
||||
fc.AddField(FieldType::App, "Git_Revision", Common::g_scm_rev); |
|
||||
fc.AddField(FieldType::App, "BuildDate", Common::g_build_date); |
|
||||
fc.AddField(FieldType::App, "BuildName", Common::g_build_name); |
|
||||
} |
|
||||
|
|
||||
void AppendCPUInfo(FieldCollection& fc) { |
|
||||
#ifdef ARCHITECTURE_x86_64
|
|
||||
|
|
||||
const auto& caps = Common::GetCPUCaps(); |
|
||||
const auto add_field = [&fc](std::string_view field_name, const auto& field_value) { |
|
||||
fc.AddField(FieldType::UserSystem, field_name, field_value); |
|
||||
}; |
|
||||
add_field("CPU_Model", caps.cpu_string); |
|
||||
add_field("CPU_BrandString", caps.brand_string); |
|
||||
|
|
||||
add_field("CPU_Extension_x64_SSE", caps.sse); |
|
||||
add_field("CPU_Extension_x64_SSE2", caps.sse2); |
|
||||
add_field("CPU_Extension_x64_SSE3", caps.sse3); |
|
||||
add_field("CPU_Extension_x64_SSSE3", caps.ssse3); |
|
||||
add_field("CPU_Extension_x64_SSE41", caps.sse4_1); |
|
||||
add_field("CPU_Extension_x64_SSE42", caps.sse4_2); |
|
||||
|
|
||||
add_field("CPU_Extension_x64_AVX", caps.avx); |
|
||||
add_field("CPU_Extension_x64_AVX_VNNI", caps.avx_vnni); |
|
||||
add_field("CPU_Extension_x64_AVX2", caps.avx2); |
|
||||
|
|
||||
// Skylake-X/SP level AVX512, for compatibility with the previous telemetry field
|
|
||||
add_field("CPU_Extension_x64_AVX512", |
|
||||
caps.avx512f && caps.avx512cd && caps.avx512vl && caps.avx512dq && caps.avx512bw); |
|
||||
|
|
||||
add_field("CPU_Extension_x64_AVX512F", caps.avx512f); |
|
||||
add_field("CPU_Extension_x64_AVX512CD", caps.avx512cd); |
|
||||
add_field("CPU_Extension_x64_AVX512VL", caps.avx512vl); |
|
||||
add_field("CPU_Extension_x64_AVX512DQ", caps.avx512dq); |
|
||||
add_field("CPU_Extension_x64_AVX512BW", caps.avx512bw); |
|
||||
add_field("CPU_Extension_x64_AVX512BITALG", caps.avx512bitalg); |
|
||||
add_field("CPU_Extension_x64_AVX512VBMI", caps.avx512vbmi); |
|
||||
|
|
||||
add_field("CPU_Extension_x64_AES", caps.aes); |
|
||||
add_field("CPU_Extension_x64_BMI1", caps.bmi1); |
|
||||
add_field("CPU_Extension_x64_BMI2", caps.bmi2); |
|
||||
add_field("CPU_Extension_x64_F16C", caps.f16c); |
|
||||
add_field("CPU_Extension_x64_FMA", caps.fma); |
|
||||
add_field("CPU_Extension_x64_FMA4", caps.fma4); |
|
||||
add_field("CPU_Extension_x64_GFNI", caps.gfni); |
|
||||
add_field("CPU_Extension_x64_INVARIANT_TSC", caps.invariant_tsc); |
|
||||
add_field("CPU_Extension_x64_LZCNT", caps.lzcnt); |
|
||||
add_field("CPU_Extension_x64_MONITORX", caps.monitorx); |
|
||||
add_field("CPU_Extension_x64_MOVBE", caps.movbe); |
|
||||
add_field("CPU_Extension_x64_PCLMULQDQ", caps.pclmulqdq); |
|
||||
add_field("CPU_Extension_x64_POPCNT", caps.popcnt); |
|
||||
add_field("CPU_Extension_x64_SHA", caps.sha); |
|
||||
add_field("CPU_Extension_x64_WAITPKG", caps.waitpkg); |
|
||||
#else
|
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Model", "Other"); |
|
||||
#endif
|
|
||||
} |
|
||||
|
|
||||
void AppendOSInfo(FieldCollection& fc) { |
|
||||
#ifdef __APPLE__
|
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Apple"); |
|
||||
#elif defined(_WIN32)
|
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Windows"); |
|
||||
#elif defined(__linux__) || defined(linux) || defined(__linux)
|
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Linux"); |
|
||||
#else
|
|
||||
fc.AddField(FieldType::UserSystem, "OsPlatform", "Unknown"); |
|
||||
#endif
|
|
||||
} |
|
||||
|
|
||||
} // namespace Common::Telemetry
|
|
||||
@ -1,209 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <chrono> |
|
||||
#include <map> |
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace Common::Telemetry { |
|
||||
|
|
||||
/// Field type, used for grouping fields together in the final submitted telemetry log |
|
||||
enum class FieldType : u8 { |
|
||||
None = 0, ///< No specified field group |
|
||||
App, ///< yuzu application fields (e.g. version, branch, etc.) |
|
||||
Session, ///< Emulated session fields (e.g. title ID, log, etc.) |
|
||||
Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.) |
|
||||
UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.) |
|
||||
UserConfig, ///< User configuration fields (e.g. emulated CPU core, renderer, etc.) |
|
||||
UserSystem, ///< User system information (e.g. host CPU type, RAM, etc.) |
|
||||
}; |
|
||||
|
|
||||
struct VisitorInterface; |
|
||||
|
|
||||
/** |
|
||||
* Interface class for telemetry data fields. |
|
||||
*/ |
|
||||
class FieldInterface { |
|
||||
public: |
|
||||
virtual ~FieldInterface() = default; |
|
||||
|
|
||||
/** |
|
||||
* Accept method for the visitor pattern. |
|
||||
* @param visitor Reference to the visitor that will visit this field. |
|
||||
*/ |
|
||||
virtual void Accept(VisitorInterface& visitor) const = 0; |
|
||||
|
|
||||
/** |
|
||||
* Gets the name of this field. |
|
||||
* @returns Name of this field as a string. |
|
||||
*/ |
|
||||
virtual const std::string& GetName() const = 0; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Represents a telemetry data field, i.e. a unit of data that gets logged and submitted to our |
|
||||
* telemetry web service. |
|
||||
*/ |
|
||||
template <typename T> |
|
||||
class Field : public FieldInterface { |
|
||||
public: |
|
||||
YUZU_NON_COPYABLE(Field); |
|
||||
|
|
||||
Field(FieldType type_, std::string_view name_, T value_) |
|
||||
: name(name_), type(type_), value(std::move(value_)) {} |
|
||||
|
|
||||
~Field() override = default; |
|
||||
|
|
||||
Field(Field&&) noexcept = default; |
|
||||
Field& operator=(Field&& other) noexcept = default; |
|
||||
|
|
||||
void Accept(VisitorInterface& visitor) const override; |
|
||||
|
|
||||
[[nodiscard]] const std::string& GetName() const override { |
|
||||
return name; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Returns the type of the field. |
|
||||
*/ |
|
||||
[[nodiscard]] FieldType GetType() const { |
|
||||
return type; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Returns the value of the field. |
|
||||
*/ |
|
||||
[[nodiscard]] const T& GetValue() const { |
|
||||
return value; |
|
||||
} |
|
||||
|
|
||||
[[nodiscard]] bool operator==(const Field& other) const { |
|
||||
return (type == other.type) && (name == other.name) && (value == other.value); |
|
||||
} |
|
||||
|
|
||||
[[nodiscard]] bool operator!=(const Field& other) const { |
|
||||
return !operator==(other); |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
std::string name; ///< Field name, must be unique |
|
||||
FieldType type{}; ///< Field type, used for grouping fields together |
|
||||
T value; ///< Field value |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Collection of data fields that have been logged. |
|
||||
*/ |
|
||||
class FieldCollection final { |
|
||||
public: |
|
||||
YUZU_NON_COPYABLE(FieldCollection); |
|
||||
|
|
||||
FieldCollection() = default; |
|
||||
~FieldCollection() = default; |
|
||||
|
|
||||
FieldCollection(FieldCollection&&) noexcept = default; |
|
||||
FieldCollection& operator=(FieldCollection&&) noexcept = default; |
|
||||
|
|
||||
/** |
|
||||
* Accept method for the visitor pattern, visits each field in the collection. |
|
||||
* @param visitor Reference to the visitor that will visit each field. |
|
||||
*/ |
|
||||
void Accept(VisitorInterface& visitor) const; |
|
||||
|
|
||||
/** |
|
||||
* Creates a new field and adds it to the field collection. |
|
||||
* @param type Type of the field to add. |
|
||||
* @param name Name of the field to add. |
|
||||
* @param value Value for the field to add. |
|
||||
*/ |
|
||||
template <typename T> |
|
||||
void AddField(FieldType type, std::string_view name, T value) { |
|
||||
return AddField(std::make_unique<Field<T>>(type, name, std::move(value))); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Adds a new field to the field collection. |
|
||||
* @param field Field to add to the field collection. |
|
||||
*/ |
|
||||
void AddField(std::unique_ptr<FieldInterface> field); |
|
||||
|
|
||||
private: |
|
||||
std::map<std::string, std::unique_ptr<FieldInterface>> fields; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Telemetry fields visitor interface class. A backend to log to a web service should implement |
|
||||
* this interface. |
|
||||
*/ |
|
||||
struct VisitorInterface { |
|
||||
virtual ~VisitorInterface() = default; |
|
||||
|
|
||||
virtual void Visit(const Field<bool>& field) = 0; |
|
||||
virtual void Visit(const Field<double>& field) = 0; |
|
||||
virtual void Visit(const Field<float>& field) = 0; |
|
||||
virtual void Visit(const Field<u8>& field) = 0; |
|
||||
virtual void Visit(const Field<u16>& field) = 0; |
|
||||
virtual void Visit(const Field<u32>& field) = 0; |
|
||||
virtual void Visit(const Field<u64>& field) = 0; |
|
||||
virtual void Visit(const Field<s8>& field) = 0; |
|
||||
virtual void Visit(const Field<s16>& field) = 0; |
|
||||
virtual void Visit(const Field<s32>& field) = 0; |
|
||||
virtual void Visit(const Field<s64>& field) = 0; |
|
||||
virtual void Visit(const Field<std::string>& field) = 0; |
|
||||
virtual void Visit(const Field<const char*>& field) = 0; |
|
||||
virtual void Visit(const Field<std::chrono::microseconds>& field) = 0; |
|
||||
|
|
||||
/// Completion method, called once all fields have been visited |
|
||||
virtual void Complete() = 0; |
|
||||
virtual bool SubmitTestcase() = 0; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Empty implementation of VisitorInterface that drops all fields. Used when a functional |
|
||||
* backend implementation is not available. |
|
||||
*/ |
|
||||
struct NullVisitor final : public VisitorInterface { |
|
||||
YUZU_NON_COPYABLE(NullVisitor); |
|
||||
|
|
||||
NullVisitor() = default; |
|
||||
~NullVisitor() override = default; |
|
||||
|
|
||||
void Visit(const Field<bool>& /*field*/) override {} |
|
||||
void Visit(const Field<double>& /*field*/) override {} |
|
||||
void Visit(const Field<float>& /*field*/) override {} |
|
||||
void Visit(const Field<u8>& /*field*/) override {} |
|
||||
void Visit(const Field<u16>& /*field*/) override {} |
|
||||
void Visit(const Field<u32>& /*field*/) override {} |
|
||||
void Visit(const Field<u64>& /*field*/) override {} |
|
||||
void Visit(const Field<s8>& /*field*/) override {} |
|
||||
void Visit(const Field<s16>& /*field*/) override {} |
|
||||
void Visit(const Field<s32>& /*field*/) override {} |
|
||||
void Visit(const Field<s64>& /*field*/) override {} |
|
||||
void Visit(const Field<std::string>& /*field*/) override {} |
|
||||
void Visit(const Field<const char*>& /*field*/) override {} |
|
||||
void Visit(const Field<std::chrono::microseconds>& /*field*/) override {} |
|
||||
|
|
||||
void Complete() override {} |
|
||||
bool SubmitTestcase() override { |
|
||||
return false; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
/// Appends build-specific information to the given FieldCollection, |
|
||||
/// such as branch name, revision hash, etc. |
|
||||
void AppendBuildInfo(FieldCollection& fc); |
|
||||
|
|
||||
/// Appends CPU-specific information to the given FieldCollection, |
|
||||
/// such as instruction set extensions, etc. |
|
||||
void AppendCPUInfo(FieldCollection& fc); |
|
||||
|
|
||||
/// Appends OS-specific information to the given FieldCollection, |
|
||||
/// such as platform name, etc. |
|
||||
void AppendOSInfo(FieldCollection& fc); |
|
||||
|
|
||||
} // namespace Common::Telemetry |
|
||||
@ -1,294 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <array>
|
|
||||
|
|
||||
#include <mbedtls/ctr_drbg.h>
|
|
||||
#include <mbedtls/entropy.h>
|
|
||||
|
|
||||
#include "common/assert.h"
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/fs/file.h"
|
|
||||
#include "common/fs/fs.h"
|
|
||||
#include "common/fs/path_util.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
|
|
||||
#include "common/settings.h"
|
|
||||
#include "common/settings_enums.h"
|
|
||||
#include "core/file_sys/control_metadata.h"
|
|
||||
#include "core/file_sys/patch_manager.h"
|
|
||||
#include "core/loader/loader.h"
|
|
||||
#include "core/telemetry_session.h"
|
|
||||
|
|
||||
#ifdef ENABLE_WEB_SERVICE
|
|
||||
#include "web_service/telemetry_json.h"
|
|
||||
#include "web_service/verify_login.h"
|
|
||||
#endif
|
|
||||
|
|
||||
namespace Core { |
|
||||
|
|
||||
namespace Telemetry = Common::Telemetry; |
|
||||
|
|
||||
static u64 GenerateTelemetryId() { |
|
||||
u64 telemetry_id{}; |
|
||||
|
|
||||
mbedtls_entropy_context entropy; |
|
||||
mbedtls_entropy_init(&entropy); |
|
||||
mbedtls_ctr_drbg_context ctr_drbg; |
|
||||
static constexpr std::array<char, 18> personalization{{"yuzu Telemetry ID"}}; |
|
||||
|
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg); |
|
||||
ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, |
|
||||
reinterpret_cast<const unsigned char*>(personalization.data()), |
|
||||
personalization.size()) == 0); |
|
||||
ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id), |
|
||||
sizeof(u64)) == 0); |
|
||||
|
|
||||
mbedtls_ctr_drbg_free(&ctr_drbg); |
|
||||
mbedtls_entropy_free(&entropy); |
|
||||
|
|
||||
return telemetry_id; |
|
||||
} |
|
||||
|
|
||||
static const char* TranslateRenderer(Settings::RendererBackend backend) { |
|
||||
switch (backend) { |
|
||||
case Settings::RendererBackend::OpenGL: |
|
||||
return "OpenGL"; |
|
||||
case Settings::RendererBackend::Vulkan: |
|
||||
return "Vulkan"; |
|
||||
case Settings::RendererBackend::Null: |
|
||||
return "Null"; |
|
||||
} |
|
||||
return "Unknown"; |
|
||||
} |
|
||||
|
|
||||
static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) { |
|
||||
switch (backend) { |
|
||||
case Settings::GpuAccuracy::Normal: |
|
||||
return "Normal"; |
|
||||
case Settings::GpuAccuracy::High: |
|
||||
return "High"; |
|
||||
case Settings::GpuAccuracy::Extreme: |
|
||||
return "Extreme"; |
|
||||
} |
|
||||
return "Unknown"; |
|
||||
} |
|
||||
|
|
||||
static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) { |
|
||||
switch (backend) { |
|
||||
case Settings::NvdecEmulation::Off: |
|
||||
return "Off"; |
|
||||
case Settings::NvdecEmulation::Cpu: |
|
||||
return "CPU"; |
|
||||
case Settings::NvdecEmulation::Gpu: |
|
||||
return "GPU"; |
|
||||
} |
|
||||
return "Unknown"; |
|
||||
} |
|
||||
|
|
||||
static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) { |
|
||||
switch (mode) { |
|
||||
case Settings::VSyncMode::Immediate: |
|
||||
return "Immediate"; |
|
||||
case Settings::VSyncMode::Mailbox: |
|
||||
return "Mailbox"; |
|
||||
case Settings::VSyncMode::Fifo: |
|
||||
return "FIFO"; |
|
||||
case Settings::VSyncMode::FifoRelaxed: |
|
||||
return "FIFO Relaxed"; |
|
||||
} |
|
||||
return "Unknown"; |
|
||||
} |
|
||||
|
|
||||
static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) { |
|
||||
switch (mode) { |
|
||||
case Settings::AstcDecodeMode::Cpu: |
|
||||
return "CPU"; |
|
||||
case Settings::AstcDecodeMode::Gpu: |
|
||||
return "GPU"; |
|
||||
case Settings::AstcDecodeMode::CpuAsynchronous: |
|
||||
return "CPU Asynchronous"; |
|
||||
} |
|
||||
return "Unknown"; |
|
||||
} |
|
||||
|
|
||||
u64 GetTelemetryId() { |
|
||||
u64 telemetry_id{}; |
|
||||
const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; |
|
||||
|
|
||||
bool generate_new_id = !Common::FS::Exists(filename); |
|
||||
|
|
||||
if (!generate_new_id) { |
|
||||
Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Read, |
|
||||
Common::FS::FileType::BinaryFile}; |
|
||||
|
|
||||
if (!file.IsOpen()) { |
|
||||
LOG_ERROR(Core, "failed to open telemetry_id: {}", |
|
||||
Common::FS::PathToUTF8String(filename)); |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
if (!file.ReadObject(telemetry_id) || telemetry_id == 0) { |
|
||||
LOG_ERROR(Frontend, "telemetry_id is 0. Generating a new one.", telemetry_id); |
|
||||
generate_new_id = true; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (generate_new_id) { |
|
||||
Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, |
|
||||
Common::FS::FileType::BinaryFile}; |
|
||||
|
|
||||
if (!file.IsOpen()) { |
|
||||
LOG_ERROR(Core, "failed to open telemetry_id: {}", |
|
||||
Common::FS::PathToUTF8String(filename)); |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
telemetry_id = GenerateTelemetryId(); |
|
||||
|
|
||||
if (!file.WriteObject(telemetry_id)) { |
|
||||
LOG_ERROR(Core, "Failed to write telemetry_id to file."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return telemetry_id; |
|
||||
} |
|
||||
|
|
||||
u64 RegenerateTelemetryId() { |
|
||||
const u64 new_telemetry_id{GenerateTelemetryId()}; |
|
||||
const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; |
|
||||
|
|
||||
Common::FS::IOFile file{filename, Common::FS::FileAccessMode::Write, |
|
||||
Common::FS::FileType::BinaryFile}; |
|
||||
|
|
||||
if (!file.IsOpen()) { |
|
||||
LOG_ERROR(Core, "failed to open telemetry_id: {}", Common::FS::PathToUTF8String(filename)); |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
if (!file.WriteObject(new_telemetry_id)) { |
|
||||
LOG_ERROR(Core, "Failed to write telemetry_id to file."); |
|
||||
} |
|
||||
|
|
||||
return new_telemetry_id; |
|
||||
} |
|
||||
|
|
||||
bool VerifyLogin(const std::string& username, const std::string& token) { |
|
||||
#ifdef ENABLE_WEB_SERVICE
|
|
||||
return WebService::VerifyLogin(Settings::values.web_api_url.GetValue(), username, token); |
|
||||
#else
|
|
||||
return false; |
|
||||
#endif
|
|
||||
} |
|
||||
|
|
||||
TelemetrySession::TelemetrySession() = default; |
|
||||
|
|
||||
TelemetrySession::~TelemetrySession() { |
|
||||
// Log one-time session end information
|
|
||||
const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>( |
|
||||
std::chrono::system_clock::now().time_since_epoch()) |
|
||||
.count()}; |
|
||||
AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time); |
|
||||
|
|
||||
#ifdef ENABLE_WEB_SERVICE
|
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>( |
|
||||
Settings::values.web_api_url.GetValue(), Settings::values.yuzu_username.GetValue(), |
|
||||
Settings::values.yuzu_token.GetValue()); |
|
||||
#else
|
|
||||
auto backend = std::make_unique<Telemetry::NullVisitor>(); |
|
||||
#endif
|
|
||||
|
|
||||
// Complete the session, submitting to the web service backend if necessary
|
|
||||
field_collection.Accept(*backend); |
|
||||
if (Settings::values.enable_telemetry) { |
|
||||
backend->Complete(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, |
|
||||
const Service::FileSystem::FileSystemController& fsc, |
|
||||
const FileSys::ContentProvider& content_provider) { |
|
||||
// Log one-time top-level information
|
|
||||
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId()); |
|
||||
|
|
||||
// Log one-time session start information
|
|
||||
const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>( |
|
||||
std::chrono::system_clock::now().time_since_epoch()) |
|
||||
.count()}; |
|
||||
AddField(Telemetry::FieldType::Session, "Init_Time", init_time); |
|
||||
|
|
||||
u64 program_id{}; |
|
||||
const Loader::ResultStatus res{app_loader.ReadProgramId(program_id)}; |
|
||||
if (res == Loader::ResultStatus::Success) { |
|
||||
const std::string formatted_program_id{fmt::format("{:016X}", program_id)}; |
|
||||
AddField(Telemetry::FieldType::Session, "ProgramId", formatted_program_id); |
|
||||
|
|
||||
std::string name; |
|
||||
app_loader.ReadTitle(name); |
|
||||
|
|
||||
if (name.empty()) { |
|
||||
const auto metadata = [&content_provider, &fsc, program_id] { |
|
||||
const FileSys::PatchManager pm{program_id, fsc, content_provider}; |
|
||||
return pm.GetControlMetadata(); |
|
||||
}(); |
|
||||
if (metadata.first != nullptr) { |
|
||||
name = metadata.first->GetApplicationName(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!name.empty()) { |
|
||||
AddField(Telemetry::FieldType::Session, "ProgramName", name); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
AddField(Telemetry::FieldType::Session, "ProgramFormat", |
|
||||
static_cast<u8>(app_loader.GetFileType())); |
|
||||
|
|
||||
// Log application information
|
|
||||
Telemetry::AppendBuildInfo(field_collection); |
|
||||
|
|
||||
// Log user system information
|
|
||||
Telemetry::AppendCPUInfo(field_collection); |
|
||||
Telemetry::AppendOSInfo(field_collection); |
|
||||
|
|
||||
// Log user configuration information
|
|
||||
constexpr auto field_type = Telemetry::FieldType::UserConfig; |
|
||||
AddField(field_type, "Audio_SinkId", |
|
||||
Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue())); |
|
||||
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue()); |
|
||||
AddField(field_type, "Renderer_Backend", |
|
||||
TranslateRenderer(Settings::values.renderer_backend.GetValue())); |
|
||||
AddField(field_type, "Renderer_UseSpeedLimit", Settings::values.use_speed_limit.GetValue()); |
|
||||
AddField(field_type, "Renderer_SpeedLimit", Settings::values.speed_limit.GetValue()); |
|
||||
AddField(field_type, "Renderer_UseDiskShaderCache", |
|
||||
Settings::values.use_disk_shader_cache.GetValue()); |
|
||||
AddField(field_type, "Renderer_GPUAccuracyLevel", |
|
||||
TranslateGPUAccuracyLevel(Settings::values.gpu_accuracy.GetValue())); |
|
||||
AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", |
|
||||
Settings::values.use_asynchronous_gpu_emulation.GetValue()); |
|
||||
AddField(field_type, "Renderer_NvdecEmulation", |
|
||||
TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue())); |
|
||||
AddField(field_type, "Renderer_AccelerateASTC", |
|
||||
TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue())); |
|
||||
AddField(field_type, "Renderer_UseVsync", |
|
||||
TranslateVSyncMode(Settings::values.vsync_mode.GetValue())); |
|
||||
AddField(field_type, "Renderer_ShaderBackend", |
|
||||
static_cast<u32>(Settings::values.shader_backend.GetValue())); |
|
||||
AddField(field_type, "Renderer_UseAsynchronousShaders", |
|
||||
Settings::values.use_asynchronous_shaders.GetValue()); |
|
||||
AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode()); |
|
||||
} |
|
||||
|
|
||||
bool TelemetrySession::SubmitTestcase() { |
|
||||
#ifdef ENABLE_WEB_SERVICE
|
|
||||
auto backend = std::make_unique<WebService::TelemetryJson>( |
|
||||
Settings::values.web_api_url.GetValue(), Settings::values.yuzu_username.GetValue(), |
|
||||
Settings::values.yuzu_token.GetValue()); |
|
||||
field_collection.Accept(*backend); |
|
||||
return backend->SubmitTestcase(); |
|
||||
#else
|
|
||||
return false; |
|
||||
#endif
|
|
||||
} |
|
||||
|
|
||||
} // namespace Core
|
|
||||
@ -1,101 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <string> |
|
||||
#include "common/telemetry.h" |
|
||||
|
|
||||
namespace FileSys { |
|
||||
class ContentProvider; |
|
||||
} |
|
||||
|
|
||||
namespace Loader { |
|
||||
class AppLoader; |
|
||||
} |
|
||||
|
|
||||
namespace Service::FileSystem { |
|
||||
class FileSystemController; |
|
||||
} |
|
||||
|
|
||||
namespace Core { |
|
||||
|
|
||||
/** |
|
||||
* Instruments telemetry for this emulation session. Creates a new set of telemetry fields on each |
|
||||
* session, logging any one-time fields. Interfaces with the telemetry backend used for submitting |
|
||||
* data to the web service. Submits session data on close. |
|
||||
*/ |
|
||||
class TelemetrySession { |
|
||||
public: |
|
||||
explicit TelemetrySession(); |
|
||||
~TelemetrySession(); |
|
||||
|
|
||||
TelemetrySession(const TelemetrySession&) = delete; |
|
||||
TelemetrySession& operator=(const TelemetrySession&) = delete; |
|
||||
|
|
||||
TelemetrySession(TelemetrySession&&) = delete; |
|
||||
TelemetrySession& operator=(TelemetrySession&&) = delete; |
|
||||
|
|
||||
/** |
|
||||
* Adds the initial telemetry info necessary when starting up a title. |
|
||||
* |
|
||||
* This includes information such as: |
|
||||
* - Telemetry ID |
|
||||
* - Initialization time |
|
||||
* - Title ID |
|
||||
* - Title name |
|
||||
* - Title file format |
|
||||
* - Miscellaneous settings values. |
|
||||
* |
|
||||
* @param app_loader The application loader to use to retrieve |
|
||||
* title-specific information. |
|
||||
* @param fsc Filesystem controller to use to retrieve info. |
|
||||
* @param content_provider Content provider to use to retrieve info. |
|
||||
*/ |
|
||||
void AddInitialInfo(Loader::AppLoader& app_loader, |
|
||||
const Service::FileSystem::FileSystemController& fsc, |
|
||||
const FileSys::ContentProvider& content_provider); |
|
||||
|
|
||||
/** |
|
||||
* Wrapper around the Telemetry::FieldCollection::AddField method. |
|
||||
* @param type Type of the field to add. |
|
||||
* @param name Name of the field to add. |
|
||||
* @param value Value for the field to add. |
|
||||
*/ |
|
||||
template <typename T> |
|
||||
void AddField(Common::Telemetry::FieldType type, const char* name, T value) { |
|
||||
field_collection.AddField(type, name, std::move(value)); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Submits a Testcase. |
|
||||
* @returns A bool indicating whether the submission succeeded |
|
||||
*/ |
|
||||
bool SubmitTestcase(); |
|
||||
|
|
||||
private: |
|
||||
/// Tracks all added fields for the session |
|
||||
Common::Telemetry::FieldCollection field_collection; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Gets TelemetryId, a unique identifier used for the user's telemetry sessions. |
|
||||
* @returns The current TelemetryId for the session. |
|
||||
*/ |
|
||||
u64 GetTelemetryId(); |
|
||||
|
|
||||
/** |
|
||||
* Regenerates TelemetryId, a unique identifier used for the user's telemetry sessions. |
|
||||
* @returns The new TelemetryId that was generated. |
|
||||
*/ |
|
||||
u64 RegenerateTelemetryId(); |
|
||||
|
|
||||
/** |
|
||||
* Verifies the username and token. |
|
||||
* @param username yuzu username to use for authentication. |
|
||||
* @param token yuzu token to use for authentication. |
|
||||
* @returns Future with bool indicating whether the verification succeeded |
|
||||
*/ |
|
||||
bool VerifyLogin(const std::string& username, const std::string& token); |
|
||||
|
|
||||
} // namespace Core |
|
||||
@ -1,130 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <nlohmann/json.hpp>
|
|
||||
#include "common/detached_tasks.h"
|
|
||||
#include "web_service/telemetry_json.h"
|
|
||||
#include "web_service/web_backend.h"
|
|
||||
#include "web_service/web_result.h"
|
|
||||
|
|
||||
namespace WebService { |
|
||||
|
|
||||
namespace Telemetry = Common::Telemetry; |
|
||||
|
|
||||
struct TelemetryJson::Impl { |
|
||||
Impl(std::string host_, std::string username_, std::string token_) |
|
||||
: host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {} |
|
||||
|
|
||||
nlohmann::json& TopSection() { |
|
||||
return sections[static_cast<u8>(Telemetry::FieldType::None)]; |
|
||||
} |
|
||||
|
|
||||
const nlohmann::json& TopSection() const { |
|
||||
return sections[static_cast<u8>(Telemetry::FieldType::None)]; |
|
||||
} |
|
||||
|
|
||||
template <class T> |
|
||||
void Serialize(Telemetry::FieldType type, const std::string& name, T value) { |
|
||||
sections[static_cast<u8>(type)][name] = value; |
|
||||
} |
|
||||
|
|
||||
void SerializeSection(Telemetry::FieldType type, const std::string& name) { |
|
||||
TopSection()[name] = sections[static_cast<unsigned>(type)]; |
|
||||
} |
|
||||
|
|
||||
nlohmann::json output; |
|
||||
std::array<nlohmann::json, 7> sections; |
|
||||
std::string host; |
|
||||
std::string username; |
|
||||
std::string token; |
|
||||
}; |
|
||||
|
|
||||
TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token) |
|
||||
: impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {} |
|
||||
TelemetryJson::~TelemetryJson() = default; |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<double>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<float>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { |
|
||||
impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count()); |
|
||||
} |
|
||||
|
|
||||
void TelemetryJson::Complete() { |
|
||||
impl->SerializeSection(Telemetry::FieldType::App, "App"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::Session, "Session"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::Performance, "Performance"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); |
|
||||
|
|
||||
auto content = impl->TopSection().dump(); |
|
||||
// Send the telemetry async but don't handle the errors since they were written to the log
|
|
||||
Common::DetachedTasks::AddTask([host{impl->host}, content]() { |
|
||||
Client{host, "", ""}.PostJson("/telemetry", content, true); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
bool TelemetryJson::SubmitTestcase() { |
|
||||
impl->SerializeSection(Telemetry::FieldType::App, "App"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::Session, "Session"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); |
|
||||
impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); |
|
||||
|
|
||||
auto content = impl->TopSection().dump(); |
|
||||
Client client(impl->host, impl->username, impl->token); |
|
||||
auto value = client.PostJson("/gamedb/testcase", content, false); |
|
||||
|
|
||||
return value.result_code == WebResult::Code::Success; |
|
||||
} |
|
||||
|
|
||||
} // namespace WebService
|
|
||||
@ -1,44 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <chrono> |
|
||||
#include <string> |
|
||||
#include "common/telemetry.h" |
|
||||
|
|
||||
namespace WebService { |
|
||||
|
|
||||
/** |
|
||||
* Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the |
|
||||
* yuzu web service |
|
||||
*/ |
|
||||
class TelemetryJson : public Common::Telemetry::VisitorInterface { |
|
||||
public: |
|
||||
TelemetryJson(std::string host, std::string username, std::string token); |
|
||||
~TelemetryJson() override; |
|
||||
|
|
||||
void Visit(const Common::Telemetry::Field<bool>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<double>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<float>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<u8>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<u16>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<u32>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<u64>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<s8>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<s16>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<s32>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<s64>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<std::string>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<const char*>& field) override; |
|
||||
void Visit(const Common::Telemetry::Field<std::chrono::microseconds>& field) override; |
|
||||
|
|
||||
void Complete() override; |
|
||||
bool SubmitTestcase() override; |
|
||||
|
|
||||
private: |
|
||||
struct Impl; |
|
||||
std::unique_ptr<Impl> impl; |
|
||||
}; |
|
||||
|
|
||||
} // namespace WebService |
|
||||
@ -0,0 +1,3 @@ |
|||||
|
#! /bin/sh |
||||
|
|
||||
|
exec find src -iname *.h -o -iname *.cpp | xargs clang-format-15 -i -style=file:src/.clang-format |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue