mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Add perf stat logging through ini setting
For better tracking of performance regressions on incoming changes, this change adds a way to dump frametime to file by changing an ini config option. This is intentionally hidden as its only useful to a small number of individuals, and not really applicable to the general userbase.
This commit is contained in:
		
							parent
							
								
									84b40f2da6
								
							
						
					
					
						commit
						62e6c147ae
					
				
					 11 changed files with 78 additions and 11 deletions
				
			
		|  | @ -146,6 +146,12 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st | |||
|         } | ||||
|     } | ||||
|     cheat_engine = std::make_unique<Cheats::CheatEngine>(*this); | ||||
|     u64 title_id{0}; | ||||
|     if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) { | ||||
|         LOG_ERROR(Core, "Failed to find title id for ROM (Error {})", | ||||
|                   static_cast<u32>(load_result)); | ||||
|     } | ||||
|     perf_stats = std::make_unique<PerfStats>(title_id); | ||||
|     status = ResultStatus::Success; | ||||
|     m_emu_window = &emu_window; | ||||
|     m_filepath = filepath; | ||||
|  | @ -158,7 +164,7 @@ void System::PrepareReschedule() { | |||
| } | ||||
| 
 | ||||
| PerfStats::Results System::GetAndResetPerfStats() { | ||||
|     return perf_stats.GetAndResetStats(timing->GetGlobalTimeUs()); | ||||
|     return perf_stats->GetAndResetStats(timing->GetGlobalTimeUs()); | ||||
| } | ||||
| 
 | ||||
| void System::Reschedule() { | ||||
|  | @ -177,8 +183,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo | |||
| 
 | ||||
|     timing = std::make_unique<Timing>(); | ||||
| 
 | ||||
|     kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, | ||||
|                                                     [this] { PrepareReschedule(); }, system_mode); | ||||
|     kernel = std::make_unique<Kernel::KernelSystem>( | ||||
|         *memory, *timing, [this] { PrepareReschedule(); }, system_mode); | ||||
| 
 | ||||
|     if (Settings::values.use_cpu_jit) { | ||||
| #ifdef ARCHITECTURE_x86_64 | ||||
|  | @ -231,7 +237,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo | |||
| 
 | ||||
|     // Reset counters and set time origin to current frame
 | ||||
|     GetAndResetPerfStats(); | ||||
|     perf_stats.BeginSystemFrame(); | ||||
|     perf_stats->BeginSystemFrame(); | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
|  |  | |||
|  | @ -216,7 +216,7 @@ public: | |||
|     /// Gets a const reference to the video dumper backend
 | ||||
|     const VideoDumper::Backend& VideoDumper() const; | ||||
| 
 | ||||
|     PerfStats perf_stats; | ||||
|     std::unique_ptr<PerfStats> perf_stats = std::make_unique<PerfStats>(0); | ||||
|     FrameLimiter frame_limiter; | ||||
| 
 | ||||
|     void SetStatus(ResultStatus new_status, const char* details = nullptr) { | ||||
|  |  | |||
|  | @ -307,7 +307,7 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) { | |||
| 
 | ||||
|     if (screen_id == 0) { | ||||
|         MicroProfileFlip(); | ||||
|         Core::System::GetInstance().perf_stats.EndGameFrame(); | ||||
|         Core::System::GetInstance().perf_stats->EndGameFrame(); | ||||
|     } | ||||
| 
 | ||||
|     return RESULT_SUCCESS; | ||||
|  |  | |||
|  | @ -228,7 +228,7 @@ void Module::UpdateGyroscopeCallback(u64 userdata, s64 cycles_late) { | |||
| 
 | ||||
|     Common::Vec3<float> gyro; | ||||
|     std::tie(std::ignore, gyro) = motion_device->GetStatus(); | ||||
|     double stretch = system.perf_stats.GetLastFrameTimeScale(); | ||||
|     double stretch = system.perf_stats->GetLastFrameTimeScale(); | ||||
|     gyro *= gyroscope_coef * static_cast<float>(stretch); | ||||
|     gyroscope_entry.x = static_cast<s16>(gyro.x); | ||||
|     gyroscope_entry.y = static_cast<s16>(gyro.y); | ||||
|  |  | |||
|  | @ -4,8 +4,12 @@ | |||
| 
 | ||||
| #include <algorithm> | ||||
| #include <chrono> | ||||
| #include <iterator> | ||||
| #include <mutex> | ||||
| #include <numeric> | ||||
| #include <thread> | ||||
| #include <fmt/format.h> | ||||
| #include "common/file_util.h" | ||||
| #include "core/hw/gpu.h" | ||||
| #include "core/perf_stats.h" | ||||
| #include "core/settings.h" | ||||
|  | @ -15,8 +19,26 @@ using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; | |||
| using std::chrono::duration_cast; | ||||
| using std::chrono::microseconds; | ||||
| 
 | ||||
| // Purposefully ignore the first five frames, as there's a significant amount of overhead in
 | ||||
| // booting that we shouldn't account for
 | ||||
| constexpr size_t IGNORE_FRAMES = 5; | ||||
| 
 | ||||
| namespace Core { | ||||
| 
 | ||||
| PerfStats::PerfStats(u64 title_id) : title_id(title_id) {} | ||||
| 
 | ||||
| PerfStats::~PerfStats() { | ||||
|     if (Settings::values.record_frame_times && title_id != 0) { | ||||
|         std::ostringstream stream; | ||||
|         std::copy(perf_history.begin() + IGNORE_FRAMES, perf_history.begin() + current_index, | ||||
|                   std::ostream_iterator<double>(stream, "\n")); | ||||
|         std::string path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); | ||||
|         std::string filename = fmt::format("{}/{:X}.csv", path, title_id); | ||||
|         FileUtil::IOFile file(filename, "w"); | ||||
|         file.WriteString(stream.str()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void PerfStats::BeginSystemFrame() { | ||||
|     std::lock_guard lock{object_mutex}; | ||||
| 
 | ||||
|  | @ -27,7 +49,12 @@ void PerfStats::EndSystemFrame() { | |||
|     std::lock_guard lock{object_mutex}; | ||||
| 
 | ||||
|     auto frame_end = Clock::now(); | ||||
|     accumulated_frametime += frame_end - frame_begin; | ||||
|     const auto frame_time = frame_end - frame_begin; | ||||
|     if (current_index < perf_history.size()) { | ||||
|         perf_history[current_index++] = | ||||
|             std::chrono::duration<double, std::milli>(frame_time).count(); | ||||
|     } | ||||
|     accumulated_frametime += frame_time; | ||||
|     system_frames += 1; | ||||
| 
 | ||||
|     previous_frame_length = frame_end - previous_frame_end; | ||||
|  | @ -40,6 +67,15 @@ void PerfStats::EndGameFrame() { | |||
|     game_frames += 1; | ||||
| } | ||||
| 
 | ||||
| double PerfStats::GetMeanFrametime() { | ||||
|     if (current_index < 2) { | ||||
|         return 0; | ||||
|     } | ||||
|     double sum = std::accumulate(perf_history.begin() + IGNORE_FRAMES, | ||||
|                                  perf_history.begin() + current_index, 0); | ||||
|     return sum / (current_index - 1 - IGNORE_FRAMES); | ||||
| } | ||||
| 
 | ||||
| PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_us) { | ||||
|     std::lock_guard lock(object_mutex); | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <chrono> | ||||
| #include <mutex> | ||||
|  | @ -18,6 +19,10 @@ namespace Core { | |||
|  */ | ||||
| class PerfStats { | ||||
| public: | ||||
|     explicit PerfStats(u64 title_id); | ||||
| 
 | ||||
|     ~PerfStats(); | ||||
| 
 | ||||
|     using Clock = std::chrono::high_resolution_clock; | ||||
| 
 | ||||
|     struct Results { | ||||
|  | @ -37,6 +42,11 @@ public: | |||
| 
 | ||||
|     Results GetAndResetStats(std::chrono::microseconds current_system_time_us); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns the Arthimetic Mean of all frametime values stored in the performance history. | ||||
|      */ | ||||
|     double GetMeanFrametime(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Gets the ratio between walltime and the emulated time of the previous system frame. This is | ||||
|      * useful for scaling inputs or outputs moving between the two time domains. | ||||
|  | @ -44,7 +54,15 @@ public: | |||
|     double GetLastFrameTimeScale(); | ||||
| 
 | ||||
| private: | ||||
|     std::mutex object_mutex; | ||||
|     std::mutex object_mutex{}; | ||||
| 
 | ||||
|     /// Title ID for the game that is running. 0 if there is no game running yet
 | ||||
|     u64 title_id{0}; | ||||
|     /// Current index for writing to the perf_history array
 | ||||
|     size_t current_index{0}; | ||||
|     /// Stores an hour of historical frametime data useful for processing and tracking performance
 | ||||
|     /// regressions with code changes.
 | ||||
|     std::array<double, 216000> perf_history = {}; | ||||
| 
 | ||||
|     /// Point when the cumulative counters were reset
 | ||||
|     Clock::time_point reset_point = Clock::now(); | ||||
|  |  | |||
|  | @ -187,6 +187,7 @@ struct Values { | |||
|     std::array<int, Service::CAM::NumCameras> camera_flip; | ||||
| 
 | ||||
|     // Debugging
 | ||||
|     bool record_frame_times; | ||||
|     bool use_gdbstub; | ||||
|     u16 gdbstub_port; | ||||
|     std::string log_filter; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue