Nugget
Bare-metal libraries and examples for the original PlayStation
Loading...
Searching...
No Matches
profiler.hh
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29#include <stdint.h>
30
32#include "psyqo/shadow-stack.hh"
33
34extern "C" uint32_t psyqoFrameCounter;
35
36namespace psyqo {
37
76class Profiler {
77 public:
82 struct Zone {
83 const char* name;
84 const char* file;
85 uint32_t line;
86 };
87
91 template <unsigned Capacity>
92 struct Buffer {
93 static_assert(Capacity && !(Capacity & (Capacity - 1)), "Capacity must be a power of two");
96 };
97
107 template <unsigned Capacity>
109 install(&buffer.header, buffer.entries, Capacity);
110 }
111
115 static void uninstall();
116
117 static bool installed() { return s_header != nullptr; }
118
122 [[gnu::always_inline, gnu::no_instrument_function]] static inline uint32_t now() {
123 volatile uint16_t* const counter1 = reinterpret_cast<volatile uint16_t*>(0xbf801110);
124 volatile uint16_t* const counter2 = reinterpret_cast<volatile uint16_t*>(0xbf801120);
125 uint32_t lines, cycles;
126 do {
127 lines = *counter1;
128 cycles = *counter2;
129 } while (lines != *counter1);
130 return (lines << 16) | cycles;
131 }
132
136 static uint32_t elapsed(uint32_t start, uint32_t end) { return pcsx_profElapsed(start, end, s_cyclesPer16Lines); }
137
142 class Scope {
143 public:
144 [[gnu::always_inline]] explicit Scope(const Zone& zone) : m_zone(&zone) {
145 if (s_header) begin();
146 }
147 [[gnu::always_inline]] ~Scope() {
148 if (m_header) end();
149 }
150 Scope(const Scope&) = delete;
151 Scope& operator=(const Scope&) = delete;
152
153 private:
154 void begin();
155 void end();
156 const Zone* m_zone;
157 pcsx_ProfHeader* m_header = nullptr;
158 uint32_t m_start;
159 uint16_t m_vsync;
160 uint8_t m_depth;
161 uint8_t m_stackDepth;
162 uint32_t m_generation;
163 };
164
165 struct BenchResult {
166 uint32_t count = 0;
167 uint32_t min = 0;
168 uint32_t max = 0;
169 uint32_t total = 0;
170 uint32_t mean() const { return count ? total / count : 0; }
171 };
172
182 template <typename F>
183 static BenchResult bench(const Zone& zone, unsigned count, F&& function) {
185 if (!s_header || !count) return result;
186 uint32_t generation = s_generation;
187 uint8_t depth = s_depth;
188 unsigned stackDepth = ShadowStack::depth();
189 if (stackDepth > 255) stackDepth = 255;
190 result.min = 0xffffffff;
191 for (unsigned i = 0; i < count; i++) {
192 uint16_t vsync = *const_cast<const volatile uint32_t*>(&psyqoFrameCounter);
193 s_depth = depth + 1;
194 uint32_t start = now();
195 function();
196 uint32_t end = now();
197 s_depth = depth;
198 // Reinstalled from inside the run: the counters were reset under it.
199 if (s_generation != generation) break;
200 record(&zone, start, end, vsync, depth, stackDepth);
201 uint32_t cycles = elapsed(start, end);
202 cycles = cycles > s_nowCost ? cycles - s_nowCost : 0;
203 if (cycles < result.min) result.min = cycles;
204 if (cycles > result.max) result.max = cycles;
205 result.total += cycles;
206 result.count++;
207 }
208 if (!result.count) result.min = 0;
209 return result;
210 }
211
219 template <typename F>
220 static void forEach(F&& function) {
221 pcsx_ProfHeader* header = s_header;
222 if (!header) return;
223 uint32_t written = header->written;
224 uint32_t mask = header->capacity - 1;
225 uint32_t count = s_full ? header->capacity : written;
226 auto entries = reinterpret_cast<const pcsx_ProfEntry*>(header->entries);
227 for (uint32_t i = written - count; i != written; i++) {
228 const pcsx_ProfEntry& entry = entries[i & mask];
229 function(*reinterpret_cast<const Zone*>(entry.zone), entry.start, elapsed(entry.start, entry.end),
230 entry.vsync, entry.depth, entry.stackDepth);
231 }
232 }
233
240 static bool dumpPCDRV(const char* path);
241
242 private:
243 static void install(pcsx_ProfHeader* header, pcsx_ProfEntry* entries, unsigned capacity);
244 static void record(const Zone* zone, uint32_t start, uint32_t end, uint16_t vsync, uint8_t depth,
245 uint8_t stackDepth);
246 static pcsx_ProfHeader* s_header;
247 static uint32_t s_cyclesPer16Lines;
248 static uint32_t s_nowCost;
249 static uint8_t s_depth;
250 // Bumped by every install and uninstall, so scopes and benchmark runs
251 // straddling one are dropped rather than timed across a counter reset.
252 static uint32_t s_generation;
253 // The buffer has wrapped at least once since install.
254 static bool s_full;
255 friend class Scope;
256};
257
258} // namespace psyqo
259
260#define PSYQO_PROFILE_CONCAT_(a, b) a##b
261#define PSYQO_PROFILE_CONCAT(a, b) PSYQO_PROFILE_CONCAT_(a, b)
262
267#define PSYQO_PROFILE_SCOPE(name) \
268 static constexpr psyqo::Profiler::Zone PSYQO_PROFILE_CONCAT(psyqoProfileZone_, __LINE__) = {name, __FILE__, \
269 __LINE__}; \
270 psyqo::Profiler::Scope PSYQO_PROFILE_CONCAT(psyqoProfileScope_, __LINE__) { \
271 PSYQO_PROFILE_CONCAT(psyqoProfileZone_, __LINE__) \
272 }
int written
Definition cdda.c:321
uint32_t start
Definition cdlgetlocp.c:252
Times its own lifetime. Use PSYQO_PROFILE_SCOPE rather than declaring one directly.
Definition profiler.hh:142
~Scope()
Definition profiler.hh:147
Scope(const Scope &)=delete
Scope(const Zone &zone)
Definition profiler.hh:144
Scope & operator=(const Scope &)=delete
Scoped profiler: timed scopes recorded into a ring buffer.
Definition profiler.hh:76
static bool dumpPCDRV(const char *path)
Writes every entry still in the buffer to a host file through PCDRV, one line each,...
Definition profiler.cpp:199
static void forEach(F &&function)
Calls function(zone, start, cycles, vsync, depth, stackDepth) for every entry still in the buffer,...
Definition profiler.hh:220
static void uninstall()
Stops recording. Root counter 2 is left running.
Definition profiler.cpp:150
static bool installed()
Definition profiler.hh:117
static uint32_t now()
Reads a timestamp, as stored in the buffer.
Definition profiler.hh:122
static void install(Buffer< Capacity > &buffer)
Claims root counter 2 and starts recording into the buffer.
Definition profiler.hh:108
static uint32_t elapsed(uint32_t start, uint32_t end)
System clock cycles from timestamp start to timestamp end.
Definition profiler.hh:136
static BenchResult bench(const Zone &zone, unsigned count, F &&function)
Runs function count times, timing each run.
Definition profiler.hh:183
static constexpr unsigned depth()
Definition shadow-stack.hh:108
uint32_t psyqoFrameCounter
Definition gpu.hh:50
int i
Definition gte-regio.c:299
void * result
Definition memcpy.c:52
Definition lua.hh:40
uint32_t psyqoFrameCounter
Definition profiler.hh:34
Definition pcsxprof.h:66
Definition pcsxprof.h:79
Definition profiler.hh:165
uint32_t total
Definition profiler.hh:169
uint32_t min
Definition profiler.hh:167
uint32_t mean() const
Definition profiler.hh:170
uint32_t count
Definition profiler.hh:166
uint32_t max
Definition profiler.hh:168
Storage for the ring buffer, with room for Capacity scopes.
Definition profiler.hh:92
pcsx_ProfHeader header
Definition profiler.hh:94
pcsx_ProfEntry entries[Capacity]
Definition profiler.hh:95
Static descriptor of one profiled scope. Its address is its identity.
Definition profiler.hh:82
uint32_t line
Definition profiler.hh:85
const char * file
Definition profiler.hh:84
const char * name
Definition profiler.hh:83
static void * buffer
Definition syscalls.h:274
static const void size_t count
Definition syscalls.h:196
int lines
Definition timers.c:412
uint32_t elapsed
Definition timers.c:592