2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file newgrf_profiling.cpp Profiling of NewGRF action 2 handling. */
10 #include "newgrf_profiling.h"
11 #include "date_func.h"
12 #include "fileio_func.h"
13 #include "string_func.h"
14 #include "console_func.h"
15 #include "spritecache.h"
21 std::vector
<NewGRFProfiler
> _newgrf_profilers
;
22 Date _newgrf_profile_end_date
;
26 * Create profiler object and begin profiling session.
27 * @param grffile The GRF file to collect profiling data on
28 * @param end_date Game date to end profiling on
30 NewGRFProfiler::NewGRFProfiler(const GRFFile
*grffile
) : grffile
{ grffile
}, active
{ false }, cur_call
{}
35 * Complete profiling session and write data to file
37 NewGRFProfiler::~NewGRFProfiler()
42 * Capture the start of a sprite group resolution.
43 * @param resolver Data about sprite group being resolved
45 void NewGRFProfiler::BeginResolve(const ResolverObject
&resolver
)
47 using namespace std::chrono
;
48 this->cur_call
.root_sprite
= resolver
.root_spritegroup
->nfo_line
;
49 this->cur_call
.subs
= 0;
50 this->cur_call
.time
= (uint32
)time_point_cast
<microseconds
>(high_resolution_clock::now()).time_since_epoch().count();
51 this->cur_call
.tick
= _tick_counter
;
52 this->cur_call
.cb
= resolver
.callback
;
53 this->cur_call
.feat
= resolver
.GetFeature();
54 this->cur_call
.item
= resolver
.GetDebugID();
58 * Capture the completion of a sprite group resolution.
60 void NewGRFProfiler::EndResolve(const SpriteGroup
*result
)
62 using namespace std::chrono
;
63 this->cur_call
.time
= (uint32
)time_point_cast
<microseconds
>(high_resolution_clock::now()).time_since_epoch().count() - this->cur_call
.time
;
65 if (result
== nullptr) {
66 this->cur_call
.result
= 0;
67 } else if (result
->type
== SGT_CALLBACK
) {
68 this->cur_call
.result
= static_cast<const CallbackResultSpriteGroup
*>(result
)->result
;
69 } else if (result
->type
== SGT_RESULT
) {
70 this->cur_call
.result
= GetSpriteLocalID(static_cast<const ResultSpriteGroup
*>(result
)->sprite
);
72 this->cur_call
.result
= result
->nfo_line
;
75 this->calls
.push_back(this->cur_call
);
79 * Capture a recursive sprite group resolution.
81 void NewGRFProfiler::RecursiveResolve()
83 this->cur_call
.subs
+= 1;
86 void NewGRFProfiler::Start()
90 this->start_tick
= _tick_counter
;
93 uint32
NewGRFProfiler::Finish()
95 if (!this->active
) return 0;
97 if (this->calls
.empty()) {
98 IConsolePrintF(CC_DEBUG
, "Finished profile of NewGRF [%08X], no events collected, not writing a file", BSWAP32(this->grffile
->grfid
));
102 std::string filename
= this->GetOutputFilename();
103 IConsolePrintF(CC_DEBUG
, "Finished profile of NewGRF [%08X], writing %u events to %s", BSWAP32(this->grffile
->grfid
), (uint
)this->calls
.size(), filename
.c_str());
105 FILE *f
= FioFOpenFile(filename
.c_str(), "wt", Subdirectory::NO_DIRECTORY
);
106 FileCloser
fcloser(f
);
108 uint32 total_microseconds
= 0;
110 fputs("Tick,Sprite,Feature,Item,CallbackID,Microseconds,Depth,Result\n", f
);
111 for (const Call
&c
: this->calls
) {
112 fprintf(f
, "%u,%u,0x%X,%u,0x%X,%u,%u,%u\n", c
.tick
, c
.root_sprite
, c
.feat
, c
.item
, (uint
)c
.cb
, c
.time
, c
.subs
, c
.result
);
113 total_microseconds
+= c
.time
;
118 return total_microseconds
;
121 void NewGRFProfiler::Abort()
123 this->active
= false;
128 * Get name of the file that will be written.
129 * @return File name of profiling output file.
131 std::string
NewGRFProfiler::GetOutputFilename() const
133 time_t write_time
= time(nullptr);
135 char timestamp
[16] = {};
136 strftime(timestamp
, lengthof(timestamp
), "%Y%m%d-%H%M", localtime(&write_time
));
138 char filepath
[MAX_PATH
] = {};
139 seprintf(filepath
, lastof(filepath
), "%sgrfprofile-%s-%08X.csv", FiosGetScreenshotDir(), timestamp
, BSWAP32(this->grffile
->grfid
));
141 return std::string(filepath
);
144 uint32
NewGRFProfiler::FinishAll()
147 uint32 total_microseconds
= 0;
148 for (NewGRFProfiler
&pr
: _newgrf_profilers
) {
150 total_microseconds
+= pr
.Finish();
151 max_ticks
= max(max_ticks
, _tick_counter
- pr
.start_tick
);
155 if (total_microseconds
> 0 && max_ticks
> 0) {
156 IConsolePrintF(CC_DEBUG
, "Total NewGRF callback processing: %u microseconds over %d ticks", total_microseconds
, max_ticks
);
159 _newgrf_profile_end_date
= MAX_DAY
;
161 return total_microseconds
;