mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 13:20:03 +00:00 
			
		
		
		
	Merge pull request #2683 from bunnei/telemetry-framework
Telemetry framework Part 1
This commit is contained in:
		
						commit
						634229ff45
					
				
					 9 changed files with 342 additions and 0 deletions
				
			
		|  | @ -38,6 +38,7 @@ set(SRCS | |||
|             param_package.cpp | ||||
|             scm_rev.cpp | ||||
|             string_util.cpp | ||||
|             telemetry.cpp | ||||
|             thread.cpp | ||||
|             timer.cpp | ||||
|             ) | ||||
|  | @ -74,6 +75,7 @@ set(HEADERS | |||
|             string_util.h | ||||
|             swap.h | ||||
|             synchronized_wrapper.h | ||||
|             telemetry.h | ||||
|             thread.h | ||||
|             thread_queue_list.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 | ||||
|             perf_stats.cpp | ||||
|             settings.cpp | ||||
|             telemetry_session.cpp | ||||
|             ) | ||||
| 
 | ||||
| set(HEADERS | ||||
|  | @ -366,6 +367,7 @@ set(HEADERS | |||
|             mmio.h | ||||
|             perf_stats.h | ||||
|             settings.h | ||||
|             telemetry_session.h | ||||
|             ) | ||||
| 
 | ||||
| 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); | ||||
|     } | ||||
| 
 | ||||
|     telemetry_session = std::make_unique<Core::TelemetrySession>(); | ||||
| 
 | ||||
|     CoreTiming::Init(); | ||||
|     HW::Init(); | ||||
|     Kernel::Init(system_mode); | ||||
|  | @ -162,6 +164,7 @@ void System::Shutdown() { | |||
|     CoreTiming::Shutdown(); | ||||
|     cpu_core = nullptr; | ||||
|     app_loader = nullptr; | ||||
|     telemetry_session = nullptr; | ||||
| 
 | ||||
|     LOG_DEBUG(Core, "Shutdown OK"); | ||||
| } | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include "common/common_types.h" | ||||
| #include "core/memory.h" | ||||
| #include "core/perf_stats.h" | ||||
| #include "core/telemetry_session.h" | ||||
| 
 | ||||
| class EmuWindow; | ||||
| class ARM_Interface; | ||||
|  | @ -80,6 +81,14 @@ public: | |||
|         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
 | ||||
|     void PrepareReschedule(); | ||||
| 
 | ||||
|  | @ -117,6 +126,9 @@ private: | |||
|     /// When true, signals that a reschedule should happen
 | ||||
|     bool reschedule_pending{}; | ||||
| 
 | ||||
|     /// Telemetry session for this emulation session
 | ||||
|     std::unique_ptr<Core::TelemetrySession> telemetry_session; | ||||
| 
 | ||||
|     static System s_instance; | ||||
| }; | ||||
| 
 | ||||
|  | @ -124,4 +136,8 @@ inline ARM_Interface& CPU() { | |||
|     return System::GetInstance().CPU(); | ||||
| } | ||||
| 
 | ||||
| inline TelemetrySession& Telemetry() { | ||||
|     return System::GetInstance().TelemetrySession(); | ||||
| } | ||||
| 
 | ||||
| } // namespace Core
 | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/core.h" | ||||
| #include "core/file_sys/archive_selfncch.h" | ||||
| #include "core/hle/kernel/process.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); | ||||
| 
 | ||||
|     Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id); | ||||
| 
 | ||||
|     is_loaded = true; // Set state to loaded
 | ||||
| 
 | ||||
|     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
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue