Merge pull request #2683 from bunnei/telemetry-framework
Telemetry framework Part 1
This commit is contained in:
		| @@ -38,6 +38,7 @@ set(SRCS | |||||||
|             param_package.cpp |             param_package.cpp | ||||||
|             scm_rev.cpp |             scm_rev.cpp | ||||||
|             string_util.cpp |             string_util.cpp | ||||||
|  |             telemetry.cpp | ||||||
|             thread.cpp |             thread.cpp | ||||||
|             timer.cpp |             timer.cpp | ||||||
|             ) |             ) | ||||||
| @@ -74,6 +75,7 @@ set(HEADERS | |||||||
|             string_util.h |             string_util.h | ||||||
|             swap.h |             swap.h | ||||||
|             synchronized_wrapper.h |             synchronized_wrapper.h | ||||||
|  |             telemetry.h | ||||||
|             thread.h |             thread.h | ||||||
|             thread_queue_list.h |             thread_queue_list.h | ||||||
|             timer.h |             timer.h | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								src/common/telemetry.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/common/telemetry.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <algorithm> | ||||||
|  | #include "common/telemetry.h" | ||||||
|  |  | ||||||
|  | namespace 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>; | ||||||
|  |  | ||||||
|  | } // namespace Telemetry | ||||||
							
								
								
									
										196
									
								
								src/common/telemetry.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/common/telemetry.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <chrono> | ||||||
|  | #include <map> | ||||||
|  | #include <memory> | ||||||
|  | #include <string> | ||||||
|  | #include "common/common_types.h" | ||||||
|  |  | ||||||
|  | namespace 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,          ///< Citra 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 : NonCopyable { | ||||||
|  | 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: | ||||||
|  |     Field(FieldType type, std::string name, const T& value) | ||||||
|  |         : type(type), name(std::move(name)), value(value) {} | ||||||
|  |  | ||||||
|  |     Field(FieldType type, std::string name, T&& value) | ||||||
|  |         : type(type), name(std::move(name)), value(std::move(value)) {} | ||||||
|  |  | ||||||
|  |     Field(const Field& other) : Field(other.type, other.name, other.value) {} | ||||||
|  |  | ||||||
|  |     Field& operator=(const Field& other) { | ||||||
|  |         type = other.type; | ||||||
|  |         name = other.name; | ||||||
|  |         value = other.value; | ||||||
|  |         return *this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Field& operator=(Field&& other) { | ||||||
|  |         type = other.type; | ||||||
|  |         name = std::move(other.name); | ||||||
|  |         value = std::move(other.value); | ||||||
|  |         return *this; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void Accept(VisitorInterface& visitor) const override; | ||||||
|  |  | ||||||
|  |     const std::string& GetName() const override { | ||||||
|  |         return name; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the type of the field. | ||||||
|  |      */ | ||||||
|  |     FieldType GetType() const { | ||||||
|  |         return type; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns the value of the field. | ||||||
|  |      */ | ||||||
|  |     const T& GetValue() const { | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline bool operator==(const Field<T>& other) { | ||||||
|  |         return (type == other.type) && (name == other.name) && (value == other.value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline bool operator!=(const Field<T>& other) { | ||||||
|  |         return !(*this == 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 : NonCopyable { | ||||||
|  | public: | ||||||
|  |     FieldCollection() = 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, const char* 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 : NonCopyable { | ||||||
|  |     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; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Empty implementation of VisitorInterface that drops all fields. Used when a functional | ||||||
|  |  * backend implementation is not available. | ||||||
|  |  */ | ||||||
|  | struct NullVisitor : public VisitorInterface { | ||||||
|  |     ~NullVisitor() = 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 {} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace Telemetry | ||||||
| @@ -174,6 +174,7 @@ set(SRCS | |||||||
|             memory.cpp |             memory.cpp | ||||||
|             perf_stats.cpp |             perf_stats.cpp | ||||||
|             settings.cpp |             settings.cpp | ||||||
|  |             telemetry_session.cpp | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
| set(HEADERS | set(HEADERS | ||||||
| @@ -366,6 +367,7 @@ set(HEADERS | |||||||
|             mmio.h |             mmio.h | ||||||
|             perf_stats.h |             perf_stats.h | ||||||
|             settings.h |             settings.h | ||||||
|  |             telemetry_session.h | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
| include_directories(../../externals/dynarmic/include) | include_directories(../../externals/dynarmic/include) | ||||||
|   | |||||||
| @@ -132,6 +132,8 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | |||||||
|         cpu_core = std::make_unique<ARM_DynCom>(USER32MODE); |         cpu_core = std::make_unique<ARM_DynCom>(USER32MODE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     telemetry_session = std::make_unique<Core::TelemetrySession>(); | ||||||
|  |  | ||||||
|     CoreTiming::Init(); |     CoreTiming::Init(); | ||||||
|     HW::Init(); |     HW::Init(); | ||||||
|     Kernel::Init(system_mode); |     Kernel::Init(system_mode); | ||||||
| @@ -162,6 +164,7 @@ void System::Shutdown() { | |||||||
|     CoreTiming::Shutdown(); |     CoreTiming::Shutdown(); | ||||||
|     cpu_core = nullptr; |     cpu_core = nullptr; | ||||||
|     app_loader = nullptr; |     app_loader = nullptr; | ||||||
|  |     telemetry_session = nullptr; | ||||||
|  |  | ||||||
|     LOG_DEBUG(Core, "Shutdown OK"); |     LOG_DEBUG(Core, "Shutdown OK"); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| #include "core/perf_stats.h" | #include "core/perf_stats.h" | ||||||
|  | #include "core/telemetry_session.h" | ||||||
|  |  | ||||||
| class EmuWindow; | class EmuWindow; | ||||||
| class ARM_Interface; | class ARM_Interface; | ||||||
| @@ -80,6 +81,14 @@ public: | |||||||
|         return cpu_core != nullptr; |         return cpu_core != nullptr; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns a reference to the telemetry session for this emulation session. | ||||||
|  |      * @returns Reference to the telemetry session. | ||||||
|  |      */ | ||||||
|  |     Core::TelemetrySession& TelemetrySession() const { | ||||||
|  |         return *telemetry_session; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Prepare the core emulation for a reschedule |     /// Prepare the core emulation for a reschedule | ||||||
|     void PrepareReschedule(); |     void PrepareReschedule(); | ||||||
|  |  | ||||||
| @@ -117,6 +126,9 @@ private: | |||||||
|     /// When true, signals that a reschedule should happen |     /// When true, signals that a reschedule should happen | ||||||
|     bool reschedule_pending{}; |     bool reschedule_pending{}; | ||||||
|  |  | ||||||
|  |     /// Telemetry session for this emulation session | ||||||
|  |     std::unique_ptr<Core::TelemetrySession> telemetry_session; | ||||||
|  |  | ||||||
|     static System s_instance; |     static System s_instance; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -124,4 +136,8 @@ inline ARM_Interface& CPU() { | |||||||
|     return System::GetInstance().CPU(); |     return System::GetInstance().CPU(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | inline TelemetrySession& Telemetry() { | ||||||
|  |     return System::GetInstance().TelemetrySession(); | ||||||
|  | } | ||||||
|  |  | ||||||
| } // namespace Core | } // namespace Core | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/string_util.h" | #include "common/string_util.h" | ||||||
| #include "common/swap.h" | #include "common/swap.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/file_sys/archive_selfncch.h" | #include "core/file_sys/archive_selfncch.h" | ||||||
| #include "core/hle/kernel/process.h" | #include "core/hle/kernel/process.h" | ||||||
| #include "core/hle/kernel/resource_limit.h" | #include "core/hle/kernel/resource_limit.h" | ||||||
| @@ -339,6 +340,8 @@ ResultStatus AppLoader_NCCH::Load() { | |||||||
|  |  | ||||||
|     LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); |     LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); | ||||||
|  |  | ||||||
|  |     Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id); | ||||||
|  |  | ||||||
|     is_loaded = true; // Set state to loaded |     is_loaded = true; // Set state to loaded | ||||||
|  |  | ||||||
|     result = LoadExec(); // Load the executable into memory for booting |     result = LoadExec(); // Load the executable into memory for booting | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								src/core/telemetry_session.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/core/telemetry_session.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <cstring> | ||||||
|  |  | ||||||
|  | #include "common/scm_rev.h" | ||||||
|  | #include "core/telemetry_session.h" | ||||||
|  |  | ||||||
|  | namespace Core { | ||||||
|  |  | ||||||
|  | TelemetrySession::TelemetrySession() { | ||||||
|  |     // TODO(bunnei): Replace with a backend that logs to our web service | ||||||
|  |     backend = std::make_unique<Telemetry::NullVisitor>(); | ||||||
|  |  | ||||||
|  |     // Log one-time session start information | ||||||
|  |     const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; | ||||||
|  |     const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; | ||||||
|  |     AddField(Telemetry::FieldType::Session, "StartTime", start_time); | ||||||
|  |  | ||||||
|  |     // Log one-time application information | ||||||
|  |     const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; | ||||||
|  |     AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty); | ||||||
|  |     AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch); | ||||||
|  |     AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | TelemetrySession::~TelemetrySession() { | ||||||
|  |     // Log one-time session end information | ||||||
|  |     const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; | ||||||
|  |     const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; | ||||||
|  |     AddField(Telemetry::FieldType::Session, "EndTime", end_time); | ||||||
|  |  | ||||||
|  |     // Complete the session, submitting to web service if necessary | ||||||
|  |     // This is just a placeholder to wrap up the session once the core completes and this is | ||||||
|  |     // destroyed. This will be moved elsewhere once we are actually doing real I/O with the service. | ||||||
|  |     field_collection.Accept(*backend); | ||||||
|  |     backend->Complete(); | ||||||
|  |     backend = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace Core | ||||||
							
								
								
									
										38
									
								
								src/core/telemetry_session.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/telemetry_session.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | // Copyright 2017 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include "common/telemetry.h" | ||||||
|  |  | ||||||
|  | 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 : NonCopyable { | ||||||
|  | public: | ||||||
|  |     TelemetrySession(); | ||||||
|  |     ~TelemetrySession(); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 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(Telemetry::FieldType type, const char* name, T value) { | ||||||
|  |         field_collection.AddField(type, name, std::move(value)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session | ||||||
|  |     std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | } // namespace Core | ||||||
		Reference in New Issue
	
	Block a user
	 bunnei
					bunnei