2020-01-26 12:45:51 +00:00
|
|
|
/*
|
|
|
|
* This file is part of OpenTTD.
|
|
|
|
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
|
|
|
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @file newgrf_profiling.cpp Profiling of NewGRF action 2 handling. */
|
|
|
|
|
|
|
|
#include "newgrf_profiling.h"
|
|
|
|
#include "fileio_func.h"
|
|
|
|
#include "string_func.h"
|
|
|
|
#include "console_func.h"
|
|
|
|
#include "spritecache.h"
|
2021-05-13 08:00:41 +00:00
|
|
|
#include "walltime_func.h"
|
2023-04-13 11:56:00 +00:00
|
|
|
#include "timer/timer.h"
|
2023-04-24 16:55:40 +00:00
|
|
|
#include "timer/timer_game_tick.h"
|
2020-01-26 12:45:51 +00:00
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<NewGRFProfiler> _newgrf_profilers;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create profiler object and begin profiling session.
|
|
|
|
* @param grffile The GRF file to collect profiling data on
|
|
|
|
* @param end_date Game date to end profiling on
|
|
|
|
*/
|
|
|
|
NewGRFProfiler::NewGRFProfiler(const GRFFile *grffile) : grffile{ grffile }, active{ false }, cur_call{}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Complete profiling session and write data to file
|
|
|
|
*/
|
|
|
|
NewGRFProfiler::~NewGRFProfiler()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Capture the start of a sprite group resolution.
|
|
|
|
* @param resolver Data about sprite group being resolved
|
|
|
|
*/
|
|
|
|
void NewGRFProfiler::BeginResolve(const ResolverObject &resolver)
|
|
|
|
{
|
|
|
|
using namespace std::chrono;
|
|
|
|
this->cur_call.root_sprite = resolver.root_spritegroup->nfo_line;
|
|
|
|
this->cur_call.subs = 0;
|
|
|
|
this->cur_call.time = (uint32)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count();
|
2023-04-24 16:55:40 +00:00
|
|
|
this->cur_call.tick = TimerGameTick::counter;
|
2020-01-26 12:45:51 +00:00
|
|
|
this->cur_call.cb = resolver.callback;
|
|
|
|
this->cur_call.feat = resolver.GetFeature();
|
|
|
|
this->cur_call.item = resolver.GetDebugID();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Capture the completion of a sprite group resolution.
|
|
|
|
*/
|
|
|
|
void NewGRFProfiler::EndResolve(const SpriteGroup *result)
|
|
|
|
{
|
|
|
|
using namespace std::chrono;
|
|
|
|
this->cur_call.time = (uint32)time_point_cast<microseconds>(high_resolution_clock::now()).time_since_epoch().count() - this->cur_call.time;
|
|
|
|
|
|
|
|
if (result == nullptr) {
|
|
|
|
this->cur_call.result = 0;
|
|
|
|
} else if (result->type == SGT_CALLBACK) {
|
|
|
|
this->cur_call.result = static_cast<const CallbackResultSpriteGroup *>(result)->result;
|
|
|
|
} else if (result->type == SGT_RESULT) {
|
|
|
|
this->cur_call.result = GetSpriteLocalID(static_cast<const ResultSpriteGroup *>(result)->sprite);
|
|
|
|
} else {
|
|
|
|
this->cur_call.result = result->nfo_line;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->calls.push_back(this->cur_call);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Capture a recursive sprite group resolution.
|
|
|
|
*/
|
|
|
|
void NewGRFProfiler::RecursiveResolve()
|
|
|
|
{
|
|
|
|
this->cur_call.subs += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NewGRFProfiler::Start()
|
|
|
|
{
|
|
|
|
this->Abort();
|
|
|
|
this->active = true;
|
2023-04-24 16:55:40 +00:00
|
|
|
this->start_tick = TimerGameTick::counter;
|
2020-01-26 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32 NewGRFProfiler::Finish()
|
|
|
|
{
|
|
|
|
if (!this->active) return 0;
|
|
|
|
|
|
|
|
if (this->calls.empty()) {
|
2021-06-12 19:33:01 +00:00
|
|
|
IConsolePrint(CC_DEBUG, "Finished profile of NewGRF [{:08X}], no events collected, not writing a file.", BSWAP32(this->grffile->grfid));
|
2023-05-13 16:23:23 +00:00
|
|
|
|
|
|
|
this->Abort();
|
2020-01-26 12:45:51 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string filename = this->GetOutputFilename();
|
2021-06-12 19:33:01 +00:00
|
|
|
IConsolePrint(CC_DEBUG, "Finished profile of NewGRF [{:08X}], writing {} events to '{}'.", BSWAP32(this->grffile->grfid), this->calls.size(), filename);
|
2020-01-26 12:45:51 +00:00
|
|
|
|
2020-12-06 20:11:47 +00:00
|
|
|
FILE *f = FioFOpenFile(filename, "wt", Subdirectory::NO_DIRECTORY);
|
2020-01-26 12:45:51 +00:00
|
|
|
FileCloser fcloser(f);
|
|
|
|
|
|
|
|
uint32 total_microseconds = 0;
|
|
|
|
|
|
|
|
fputs("Tick,Sprite,Feature,Item,CallbackID,Microseconds,Depth,Result\n", f);
|
|
|
|
for (const Call &c : this->calls) {
|
2023-04-19 19:38:09 +00:00
|
|
|
fputs(fmt::format("{},{},{:#X},{},{:#X},{},{},{}\n", c.tick, c.root_sprite, c.feat, c.item, (uint)c.cb, c.time, c.subs, c.result).c_str(), f);
|
2020-01-26 12:45:51 +00:00
|
|
|
total_microseconds += c.time;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->Abort();
|
|
|
|
return total_microseconds;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NewGRFProfiler::Abort()
|
|
|
|
{
|
|
|
|
this->active = false;
|
|
|
|
this->calls.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get name of the file that will be written.
|
|
|
|
* @return File name of profiling output file.
|
|
|
|
*/
|
|
|
|
std::string NewGRFProfiler::GetOutputFilename() const
|
|
|
|
{
|
|
|
|
char timestamp[16] = {};
|
2021-05-13 08:00:41 +00:00
|
|
|
LocalTime::Format(timestamp, lastof(timestamp), "%Y%m%d-%H%M");
|
2020-01-26 12:45:51 +00:00
|
|
|
|
|
|
|
char filepath[MAX_PATH] = {};
|
|
|
|
seprintf(filepath, lastof(filepath), "%sgrfprofile-%s-%08X.csv", FiosGetScreenshotDir(), timestamp, BSWAP32(this->grffile->grfid));
|
|
|
|
|
|
|
|
return std::string(filepath);
|
|
|
|
}
|
|
|
|
|
2023-05-13 21:17:11 +00:00
|
|
|
/* static */ uint32 NewGRFProfiler::FinishAll()
|
2020-01-26 12:45:51 +00:00
|
|
|
{
|
2023-05-13 21:17:11 +00:00
|
|
|
NewGRFProfiler::AbortTimer();
|
|
|
|
|
2022-09-21 10:42:29 +00:00
|
|
|
uint64 max_ticks = 0;
|
2020-01-26 12:45:51 +00:00
|
|
|
uint32 total_microseconds = 0;
|
|
|
|
for (NewGRFProfiler &pr : _newgrf_profilers) {
|
|
|
|
if (pr.active) {
|
|
|
|
total_microseconds += pr.Finish();
|
2023-04-24 16:55:40 +00:00
|
|
|
max_ticks = std::max(max_ticks, TimerGameTick::counter - pr.start_tick);
|
2020-01-26 12:45:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (total_microseconds > 0 && max_ticks > 0) {
|
2021-06-12 19:33:01 +00:00
|
|
|
IConsolePrint(CC_DEBUG, "Total NewGRF callback processing: {} microseconds over {} ticks.", total_microseconds, max_ticks);
|
2020-01-26 12:45:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return total_microseconds;
|
|
|
|
}
|
2023-04-13 11:56:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether profiling is active and should be finished.
|
|
|
|
*/
|
2023-05-13 21:17:11 +00:00
|
|
|
static TimeoutTimer<TimerGameTick> _profiling_finish_timeout(0, []()
|
2023-04-13 11:56:00 +00:00
|
|
|
{
|
|
|
|
NewGRFProfiler::FinishAll();
|
|
|
|
});
|
2023-05-13 21:17:11 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Start the timeout timer that will finish all profiling sessions.
|
|
|
|
*/
|
|
|
|
/* static */ void NewGRFProfiler::StartTimer(uint64 ticks)
|
|
|
|
{
|
|
|
|
_profiling_finish_timeout.Reset(ticks);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Abort the timeout timer, so the timer callback is never called.
|
|
|
|
*/
|
|
|
|
/* static */ void NewGRFProfiler::AbortTimer()
|
|
|
|
{
|
|
|
|
_profiling_finish_timeout.Abort();
|
|
|
|
}
|