mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Profiler.h: frames, named scopes and phases, recorded on the main thread only (other threads' scopes do nothing). Per second: frames, total and longest frame time, a mergeable frame time histogram (TrafficStats::Histogram) and time per phase. Per report: the packet types that took longest, the worst frames and the frames over a threshold with their heaviest scopes. Profiling sessions merge every frame's scope tree for a while into one (folded stacks for flame graphs). Optional Tracy client (DLU_TRACY, off by default) gets the same frames and scopes. Task 96. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
562 lines
19 KiB
C++
562 lines
19 KiB
C++
#include "Profiler.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <optional>
|
|
#include <unordered_set>
|
|
|
|
#ifdef DLU_TRACY
|
|
#include "tracy/TracyC.h"
|
|
#endif
|
|
|
|
namespace Profiler {
|
|
namespace {
|
|
thread_local bool t_Main = false;
|
|
|
|
#ifdef DLU_TRACY
|
|
// Tracy zones for the scopes (its C interface: zones with names made at run time)
|
|
uint64_t TracyBegin(const char* name, uint64_t arg) {
|
|
const auto location = ___tracy_alloc_srcloc_name(0, "", 0, "", 0, name, std::strlen(name), 0);
|
|
const auto zone = ___tracy_emit_zone_begin_alloc(location, 1);
|
|
if (arg) ___tracy_emit_zone_value(zone, arg);
|
|
return (static_cast<uint64_t>(zone.id) << 32) | static_cast<uint32_t>(zone.active);
|
|
}
|
|
|
|
void TracyEnd(uint64_t packed) {
|
|
TracyCZoneCtx zone{};
|
|
zone.id = static_cast<uint32_t>(packed >> 32);
|
|
zone.active = static_cast<int>(static_cast<uint32_t>(packed));
|
|
___tracy_emit_zone_end(zone);
|
|
}
|
|
#endif
|
|
|
|
constexpr const char* MORE = "(more)";
|
|
constexpr const char* ALL_FRAMES = "All frames";
|
|
|
|
uint32_t ClampU32(int64_t value) {
|
|
return static_cast<uint32_t>(std::clamp<int64_t>(value, 0, UINT32_MAX));
|
|
}
|
|
|
|
uint64_t Micros(int64_t ns) {
|
|
return ns > 0 ? static_cast<uint64_t>(ns / 1000) : 0;
|
|
}
|
|
|
|
std::string Duration(uint64_t us) {
|
|
char buffer[32];
|
|
if (us >= 1000000) std::snprintf(buffer, sizeof(buffer), "%.1f s", static_cast<double>(us) / 1e6);
|
|
else std::snprintf(buffer, sizeof(buffer), "%.1f ms", static_cast<double>(us) / 1e3);
|
|
return buffer;
|
|
}
|
|
|
|
bool SameName(const char* a, const char* b) {
|
|
return a == b || std::strcmp(a, b) == 0;
|
|
}
|
|
|
|
// The children of nodes[i] in a pre-order list: the following nodes one deeper, until one as shallow as it
|
|
template<typename Fn>
|
|
void ForEachChild(const std::vector<Node>& nodes, size_t i, Fn&& fn) {
|
|
for (size_t j = i + 1; j < nodes.size() && nodes[j].depth > nodes[i].depth; j++) {
|
|
if (nodes[j].depth == nodes[i].depth + 1) fn(j);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* PhaseName(size_t phase) {
|
|
static constexpr const char* NAMES[PHASES] = { "other", "packets", "entities", "physics", "replica", "scripts", "database", "cdclient", "log_flush", "web" };
|
|
return phase < PHASES ? NAMES[phase] : "";
|
|
}
|
|
|
|
void Second::Merge(const Second& other) {
|
|
ticks += other.ticks;
|
|
totalUs += other.totalUs;
|
|
maxUs = std::max(maxUs, other.maxUs);
|
|
frames.Merge(other.frames);
|
|
for (size_t i = 0; i < PHASES; i++) phaseUs[i] += other.phaseUs[i];
|
|
}
|
|
|
|
std::string DefaultLabel(const Node& node) {
|
|
if (node.name == PACKET) {
|
|
const auto key = TrafficStats::MessageKey::Unpack(node.arg);
|
|
std::string label = "Packet " + std::to_string(key.service) + ":" + std::to_string(key.packet);
|
|
if (key.gameMessage) label += ":" + std::to_string(key.gameMessage);
|
|
return label;
|
|
}
|
|
return node.arg ? node.name + " " + std::to_string(node.arg) : node.name;
|
|
}
|
|
|
|
std::string Frame::Path(const std::function<std::string(const Node&)>& label) const {
|
|
if (scopes.empty()) return "";
|
|
const auto name = [&label](const Node& node) { return label ? label(node) : DefaultLabel(node); };
|
|
std::string path;
|
|
size_t current = 0;
|
|
while (true) {
|
|
size_t heaviest = SIZE_MAX;
|
|
ForEachChild(scopes, current, [&](size_t j) {
|
|
if (heaviest == SIZE_MAX || scopes[j].totalUs > scopes[heaviest].totalUs) heaviest = j;
|
|
});
|
|
// Stop where the scope's own time is most of it
|
|
if (heaviest == SIZE_MAX || scopes[heaviest].totalUs * 5 < scopes[current].totalUs) break;
|
|
current = heaviest;
|
|
const auto& node = scopes[current];
|
|
if (!path.empty()) path += " > ";
|
|
path += name(node) + " " + Duration(node.totalUs);
|
|
if (node.count > 1) path += " x" + std::to_string(node.count);
|
|
}
|
|
// The busiest repeated scope below where the path stopped (e.g. many small lookups)
|
|
size_t repeated = SIZE_MAX;
|
|
for (size_t j = current + 1; j < scopes.size() && scopes[j].depth > scopes[current].depth; j++) {
|
|
if (scopes[j].count > 1 && (repeated == SIZE_MAX || scopes[j].totalUs > scopes[repeated].totalUs)) repeated = j;
|
|
}
|
|
if (repeated != SIZE_MAX) {
|
|
path += (path.empty() ? "" : ", ") + name(scopes[repeated]) + " " + Duration(scopes[repeated].totalUs) + " x" + std::to_string(scopes[repeated].count);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
std::string Folded(const std::vector<Node>& nodes, const std::function<std::string(const Node&)>& label) {
|
|
std::string out;
|
|
std::vector<std::string> stack;
|
|
for (size_t i = 0; i < nodes.size(); i++) {
|
|
const auto& node = nodes[i];
|
|
std::string name = label ? label(node) : DefaultLabel(node);
|
|
std::replace(name.begin(), name.end(), ';', ',');
|
|
std::replace(name.begin(), name.end(), '\n', ' ');
|
|
stack.resize(node.depth);
|
|
stack.push_back(std::move(name));
|
|
uint64_t children = 0;
|
|
ForEachChild(nodes, i, [&](size_t j) { children += nodes[j].totalUs; });
|
|
const uint64_t self = node.totalUs > children ? node.totalUs - children : 0;
|
|
if (self == 0) continue;
|
|
for (size_t d = 0; d < stack.size(); d++) {
|
|
if (d) out += ';';
|
|
out += stack[d];
|
|
}
|
|
out += ' ';
|
|
out += std::to_string(self);
|
|
out += '\n';
|
|
}
|
|
return out;
|
|
}
|
|
|
|
uint32_t Recorder::Child(std::vector<LiveNode>& nodes, uint32_t parent, const char* name, uint64_t arg, size_t maxNodes, bool& full) {
|
|
full = false;
|
|
for (uint32_t c = nodes[parent].firstChild; c; c = nodes[c].nextSibling) {
|
|
if (nodes[c].arg == arg && SameName(nodes[c].name, name)) return c;
|
|
}
|
|
// Too many different children: the rest share one
|
|
if (nodes[parent].children >= MAX_CHILDREN && !(arg == 0 && SameName(name, MORE))) {
|
|
return Child(nodes, parent, MORE, 0, maxNodes, full);
|
|
}
|
|
if (nodes.size() >= maxNodes) {
|
|
full = true;
|
|
return parent;
|
|
}
|
|
const auto index = static_cast<uint32_t>(nodes.size());
|
|
LiveNode node;
|
|
node.name = name;
|
|
node.arg = arg;
|
|
node.parent = parent;
|
|
node.nextSibling = nodes[parent].firstChild;
|
|
nodes.push_back(node);
|
|
nodes[parent].firstChild = index;
|
|
nodes[parent].children++;
|
|
return index;
|
|
}
|
|
|
|
std::vector<Node> Recorder::Flatten(const std::vector<LiveNode>& nodes, size_t limit, bool byStart, bool& truncated) {
|
|
std::vector<Node> out;
|
|
if (nodes.empty()) return out;
|
|
std::vector<char> keep(nodes.size(), 1);
|
|
truncated = nodes.size() > limit;
|
|
if (truncated) {
|
|
std::vector<uint32_t> order(nodes.size());
|
|
for (uint32_t i = 0; i < order.size(); i++) order[i] = i;
|
|
std::stable_sort(order.begin(), order.end(), [&nodes](uint32_t a, uint32_t b) { return nodes[a].totalNs > nodes[b].totalNs; });
|
|
std::fill(keep.begin(), keep.end(), 0);
|
|
keep[0] = 1;
|
|
for (size_t i = 0; i < limit && i < order.size(); i++) {
|
|
// With its parents, so the tree stays whole
|
|
for (uint32_t n = order[i]; !keep[n]; n = nodes[n].parent) keep[n] = 1;
|
|
}
|
|
}
|
|
out.reserve(std::min(limit + 8, nodes.size()));
|
|
// Pre-order, without recursion
|
|
std::vector<std::pair<uint32_t, uint8_t>> pending{ { 0u, uint8_t{ 0 } } };
|
|
std::vector<uint32_t> children;
|
|
while (!pending.empty()) {
|
|
const auto [index, depth] = pending.back();
|
|
pending.pop_back();
|
|
const auto& live = nodes[index];
|
|
Node node;
|
|
node.name = live.name ? live.name : "";
|
|
node.arg = live.arg;
|
|
node.depth = depth;
|
|
node.count = live.count;
|
|
node.totalUs = Micros(live.totalNs);
|
|
node.startUs = ClampU32(live.startNs / 1000);
|
|
out.push_back(std::move(node));
|
|
|
|
children.clear();
|
|
for (uint32_t c = live.firstChild; c; c = nodes[c].nextSibling) if (keep[c]) children.push_back(c);
|
|
if (byStart) std::sort(children.begin(), children.end(), [&nodes](uint32_t a, uint32_t b) { return nodes[a].startNs != nodes[b].startNs ? nodes[a].startNs < nodes[b].startNs : a < b; });
|
|
else std::sort(children.begin(), children.end(), [&nodes](uint32_t a, uint32_t b) { return nodes[a].totalNs != nodes[b].totalNs ? nodes[a].totalNs > nodes[b].totalNs : a < b; });
|
|
const auto childDepth = static_cast<uint8_t>(std::min(depth + 1, 255));
|
|
// Pushed in reverse so the first comes out first
|
|
for (auto it = children.rbegin(); it != children.rend(); ++it) pending.emplace_back(*it, childDepth);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void Recorder::FrameBegin(int64_t nowNs, int64_t unixMs, bool implicit) {
|
|
if (m_InFrame) return;
|
|
m_InFrame = true;
|
|
m_Implicit = implicit;
|
|
m_FrameStartNs = nowNs;
|
|
m_FrameUnixMs = unixMs;
|
|
m_Nodes.clear();
|
|
LiveNode root;
|
|
root.name = implicit ? OUTSIDE : FRAME;
|
|
m_Nodes.push_back(root);
|
|
m_Stack.clear();
|
|
m_Phase = Phase::OTHER;
|
|
m_PhaseStartNs = nowNs;
|
|
m_PhaseNs.fill(0);
|
|
}
|
|
|
|
Frame Recorder::MakeFrame(int64_t durationNs, size_t scopes) const {
|
|
Frame frame;
|
|
frame.timeMs = m_FrameUnixMs;
|
|
frame.durationUs = ClampU32(durationNs / 1000);
|
|
frame.implicit = m_Implicit;
|
|
for (size_t i = 0; i < PHASES; i++) frame.phaseUs[i] = ClampU32(m_PhaseNs[i] / 1000);
|
|
bool truncated = false;
|
|
frame.scopes = Flatten(m_Nodes, scopes, true, truncated);
|
|
return frame;
|
|
}
|
|
|
|
void Recorder::FrameEnd(int64_t nowNs) {
|
|
if (!m_InFrame) return;
|
|
// Scopes still open (a frame ended inside one) end with it
|
|
while (!m_Stack.empty()) {
|
|
const auto open = m_Stack.back();
|
|
m_Stack.pop_back();
|
|
if (open.counted) m_Nodes[open.node].totalNs += nowNs - open.startNs;
|
|
}
|
|
m_PhaseNs[static_cast<size_t>(m_Phase)] += nowNs - m_PhaseStartNs;
|
|
const int64_t duration = std::max<int64_t>(nowNs - m_FrameStartNs, 0);
|
|
m_Nodes[0].count = 1;
|
|
m_Nodes[0].totalNs = duration;
|
|
const uint32_t us = ClampU32(duration / 1000);
|
|
const uint32_t threshold = SlowThreshold();
|
|
const bool slow = threshold > 0 && us >= static_cast<uint64_t>(threshold) * 1000;
|
|
|
|
std::optional<Frame> slowFrame;
|
|
if (slow) slowFrame = MakeFrame(duration, SLOW_SCOPES);
|
|
{
|
|
std::lock_guard lock(m_Mutex);
|
|
if (!m_Implicit) {
|
|
auto& second = m_Seconds[m_FrameUnixMs / 1000];
|
|
second.time = m_FrameUnixMs / 1000;
|
|
second.ticks++;
|
|
second.totalUs += us;
|
|
second.maxUs = std::max(second.maxUs, us);
|
|
second.frames.Add(us);
|
|
for (size_t i = 0; i < PHASES; i++) second.phaseUs[i] += Micros(m_PhaseNs[i]);
|
|
}
|
|
if (m_Worst.size() < WORST_FRAMES || us > m_Worst.back().durationUs) {
|
|
auto frame = MakeFrame(duration, WORST_SCOPES);
|
|
const auto at = std::find_if(m_Worst.begin(), m_Worst.end(), [us](const Frame& f) { return f.durationUs < us; });
|
|
m_Worst.insert(at, std::move(frame));
|
|
if (m_Worst.size() > WORST_FRAMES) m_Worst.pop_back();
|
|
}
|
|
if (slowFrame && m_Slow.size() < MAX_SLOW_FRAMES) m_Slow.push_back(*slowFrame);
|
|
}
|
|
m_InFrame = false;
|
|
if (slowFrame && m_SlowSink) m_SlowSink(*slowFrame);
|
|
|
|
if (m_Session.active) {
|
|
MergeIntoSession();
|
|
m_Session.frames++;
|
|
m_Session.totalNs += duration;
|
|
if (nowNs >= m_Session.endNs) FinishSession(nowNs);
|
|
}
|
|
}
|
|
|
|
void Recorder::Enter(const char* name, uint64_t arg, int64_t nowNs) {
|
|
if (!m_InFrame) FrameBegin(nowNs, UnixMs(), true);
|
|
const uint32_t parent = m_Stack.empty() ? 0 : m_Stack.back().node;
|
|
bool full = false;
|
|
const uint32_t index = Child(m_Nodes, parent, name, arg, MAX_NODES, full);
|
|
if (full) {
|
|
m_Stack.push_back({ parent, nowNs, false });
|
|
return;
|
|
}
|
|
auto& node = m_Nodes[index];
|
|
if (node.count == 0) node.startNs = nowNs - m_FrameStartNs;
|
|
node.count++;
|
|
m_Stack.push_back({ index, nowNs, true });
|
|
}
|
|
|
|
void Recorder::Exit(int64_t nowNs) {
|
|
if (m_Stack.empty()) return;
|
|
const auto open = m_Stack.back();
|
|
m_Stack.pop_back();
|
|
if (open.counted) m_Nodes[open.node].totalNs += nowNs - open.startNs;
|
|
if (m_Stack.empty() && m_Implicit && m_InFrame) FrameEnd(nowNs);
|
|
}
|
|
|
|
Phase Recorder::SetPhase(Phase phase, int64_t nowNs) {
|
|
const auto previous = m_Phase;
|
|
if (!m_InFrame) return previous;
|
|
m_PhaseNs[static_cast<size_t>(previous)] += nowNs - m_PhaseStartNs;
|
|
m_PhaseStartNs = nowNs;
|
|
m_Phase = phase;
|
|
return previous;
|
|
}
|
|
|
|
void Recorder::Record(const char* name, uint64_t arg, int64_t durationNs, Phase phase, int64_t nowNs) {
|
|
if (!m_InFrame || durationNs < 0) return;
|
|
const uint32_t parent = m_Stack.empty() ? 0 : m_Stack.back().node;
|
|
bool full = false;
|
|
const uint32_t index = Child(m_Nodes, parent, name, arg, MAX_NODES, full);
|
|
if (!full) {
|
|
auto& node = m_Nodes[index];
|
|
if (node.count == 0) node.startNs = std::max<int64_t>(nowNs - durationNs - m_FrameStartNs, 0);
|
|
node.count++;
|
|
node.totalNs += durationNs;
|
|
}
|
|
// The time moves from the current phase to its own
|
|
if (phase != m_Phase) {
|
|
const int64_t moved = std::min(durationNs, std::max<int64_t>(nowNs - m_PhaseStartNs, 0));
|
|
m_PhaseNs[static_cast<size_t>(phase)] += moved;
|
|
m_PhaseStartNs += moved;
|
|
}
|
|
}
|
|
|
|
void Recorder::AddMessageTime(uint64_t key, int64_t durationNs) {
|
|
const auto us = Micros(durationNs);
|
|
std::lock_guard lock(m_Mutex);
|
|
auto& message = m_Messages[key];
|
|
message.key = key;
|
|
message.count++;
|
|
message.totalUs += us;
|
|
message.maxUs = std::max(message.maxUs, ClampU32(static_cast<int64_t>(us)));
|
|
}
|
|
|
|
void Recorder::SetSlowThreshold(uint32_t milliseconds) {
|
|
std::lock_guard lock(m_Mutex);
|
|
m_SlowThresholdMs = milliseconds;
|
|
}
|
|
|
|
uint32_t Recorder::SlowThreshold() const {
|
|
std::lock_guard lock(m_Mutex);
|
|
return m_SlowThresholdMs;
|
|
}
|
|
|
|
bool Recorder::StartSession(uint32_t id, uint32_t durationMs, int64_t nowNs, std::function<void(Profile&&)> done) {
|
|
if (m_Session.active) return false;
|
|
m_Session = Session{};
|
|
m_Session.active = true;
|
|
m_Session.id = id;
|
|
m_Session.startNs = nowNs;
|
|
m_Session.endNs = nowNs + static_cast<int64_t>(std::clamp<uint32_t>(durationMs, 1, MAX_SESSION_MS)) * 1000000;
|
|
LiveNode root;
|
|
root.name = ALL_FRAMES;
|
|
m_Session.nodes.push_back(root);
|
|
m_Session.done = std::move(done);
|
|
return true;
|
|
}
|
|
|
|
bool Recorder::StopSession(uint32_t id, int64_t nowNs) {
|
|
if (!m_Session.active || m_Session.id != id) return false;
|
|
FinishSession(nowNs);
|
|
return true;
|
|
}
|
|
|
|
void Recorder::CheckSession(int64_t nowNs) {
|
|
if (m_Session.active && !m_InFrame && nowNs >= m_Session.endNs) FinishSession(nowNs);
|
|
}
|
|
|
|
void Recorder::MergeIntoSession() {
|
|
auto& session = m_Session;
|
|
// Parents come before their children in m_Nodes, so each parent is mapped before its children
|
|
std::vector<uint32_t> mapped(m_Nodes.size(), 0);
|
|
for (uint32_t i = 1; i < m_Nodes.size(); i++) {
|
|
const auto& node = m_Nodes[i];
|
|
const uint32_t parent = mapped[node.parent];
|
|
bool full = false;
|
|
const uint32_t index = Child(session.nodes, parent, node.name, node.arg, MAX_SESSION_NODES, full);
|
|
mapped[i] = index;
|
|
if (full) {
|
|
// Its time stays in the parent's (the parent's total includes it)
|
|
session.truncated = true;
|
|
continue;
|
|
}
|
|
session.nodes[index].count += m_Nodes[i].count;
|
|
session.nodes[index].totalNs += m_Nodes[i].totalNs;
|
|
}
|
|
session.nodes[0].count++;
|
|
session.nodes[0].totalNs += m_Nodes[0].totalNs;
|
|
}
|
|
|
|
void Recorder::FinishSession(int64_t nowNs) {
|
|
Profile profile;
|
|
profile.id = m_Session.id;
|
|
profile.durationMs = ClampU32((nowNs - m_Session.startNs) / 1000000);
|
|
profile.frames = m_Session.frames;
|
|
profile.totalUs = Micros(m_Session.totalNs);
|
|
bool truncated = false;
|
|
profile.nodes = Flatten(m_Session.nodes, PROFILE_NODES, false, truncated);
|
|
profile.truncated = truncated || m_Session.truncated;
|
|
auto done = std::move(m_Session.done);
|
|
m_Session = Session{};
|
|
if (done) done(std::move(profile));
|
|
}
|
|
|
|
Report Recorder::Take(int64_t now) {
|
|
Report report;
|
|
report.present = true;
|
|
std::lock_guard lock(m_Mutex);
|
|
report.slowThresholdMs = m_SlowThresholdMs;
|
|
int64_t from = m_LastReported ? m_LastReported + 1 : (m_Seconds.empty() ? now : std::min(m_Seconds.begin()->first, now - 1));
|
|
from = std::max(from, now - MAX_GAP);
|
|
for (int64_t t = from; t < now; t++) {
|
|
const auto it = m_Seconds.find(t);
|
|
if (it != m_Seconds.end()) report.seconds.push_back(std::move(it->second));
|
|
else report.seconds.push_back(Second{ .time = t });
|
|
}
|
|
m_Seconds.erase(m_Seconds.begin(), m_Seconds.lower_bound(now));
|
|
if (now - 1 > m_LastReported) m_LastReported = now - 1;
|
|
|
|
report.messages.reserve(m_Messages.size());
|
|
for (const auto& [_, message] : m_Messages) report.messages.push_back(message);
|
|
m_Messages.clear();
|
|
std::sort(report.messages.begin(), report.messages.end(), [](const MessageTime& a, const MessageTime& b) {
|
|
return a.totalUs != b.totalUs ? a.totalUs > b.totalUs : a.key < b.key;
|
|
});
|
|
if (report.messages.size() > TOP_MESSAGES) report.messages.resize(TOP_MESSAGES);
|
|
|
|
report.worst = std::move(m_Worst);
|
|
m_Worst.clear();
|
|
report.slow = std::move(m_Slow);
|
|
m_Slow.clear();
|
|
return report;
|
|
}
|
|
|
|
Recorder& Local() {
|
|
static Recorder recorder;
|
|
return recorder;
|
|
}
|
|
|
|
void SetMainThread() {
|
|
t_Main = true;
|
|
}
|
|
|
|
bool IsMainThread() {
|
|
return t_Main;
|
|
}
|
|
|
|
int64_t NowNs() {
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
int64_t UnixMs() {
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
const char* Intern(const std::string& name) {
|
|
// Never freed: scope trees point at these until the process ends
|
|
static auto* names = new std::unordered_set<std::string>();
|
|
static std::mutex mutex;
|
|
std::lock_guard lock(mutex);
|
|
return names->insert(name).first->c_str();
|
|
}
|
|
|
|
void BeginFrame() {
|
|
if (t_Main) Local().FrameBegin(NowNs(), UnixMs(), false);
|
|
}
|
|
|
|
void EndFrame() {
|
|
if (!t_Main) return;
|
|
Local().FrameEnd(NowNs());
|
|
#ifdef DLU_TRACY
|
|
___tracy_emit_frame_mark(nullptr);
|
|
#endif
|
|
}
|
|
|
|
FrameScope::FrameScope() {
|
|
if (!t_Main || Local().InFrame()) return;
|
|
m_Active = true;
|
|
Local().FrameBegin(NowNs(), UnixMs(), false);
|
|
}
|
|
|
|
FrameScope::~FrameScope() {
|
|
if (!m_Active) return;
|
|
Local().FrameEnd(NowNs());
|
|
#ifdef DLU_TRACY
|
|
___tracy_emit_frame_mark(nullptr);
|
|
#endif
|
|
}
|
|
|
|
Scope::Scope(const char* name, uint64_t arg) {
|
|
if (!t_Main) return;
|
|
m_Active = true;
|
|
Local().Enter(name, arg, NowNs());
|
|
#ifdef DLU_TRACY
|
|
m_Tracy = TracyBegin(name, arg);
|
|
#endif
|
|
}
|
|
|
|
Scope::Scope(const char* name, Phase phase) {
|
|
if (!t_Main) return;
|
|
m_Active = true;
|
|
const auto now = NowNs();
|
|
auto& recorder = Local();
|
|
recorder.Enter(name, 0, now);
|
|
m_Previous = recorder.SetPhase(phase, now);
|
|
m_SetPhase = true;
|
|
#ifdef DLU_TRACY
|
|
m_Tracy = TracyBegin(name, 0);
|
|
#endif
|
|
}
|
|
|
|
Scope::~Scope() {
|
|
if (!m_Active) return;
|
|
#ifdef DLU_TRACY
|
|
TracyEnd(m_Tracy);
|
|
#endif
|
|
const auto now = NowNs();
|
|
auto& recorder = Local();
|
|
if (m_SetPhase) recorder.SetPhase(m_Previous, now);
|
|
recorder.Exit(now);
|
|
}
|
|
|
|
PacketScope::PacketScope(const uint8_t* data, size_t length) {
|
|
if (!t_Main) return;
|
|
m_Active = true;
|
|
m_Key = TrafficStats::KeyOf(data, length, false).Packed();
|
|
m_StartNs = NowNs();
|
|
auto& recorder = Local();
|
|
recorder.Enter(PACKET, m_Key, m_StartNs);
|
|
m_Previous = recorder.SetPhase(Phase::PACKETS, m_StartNs);
|
|
m_SetPhase = true;
|
|
#ifdef DLU_TRACY
|
|
m_Tracy = TracyBegin(PACKET, m_Key);
|
|
#endif
|
|
}
|
|
|
|
PacketScope::~PacketScope() {
|
|
if (!m_Active) return;
|
|
#ifdef DLU_TRACY
|
|
TracyEnd(m_Tracy);
|
|
#endif
|
|
const auto now = NowNs();
|
|
auto& recorder = Local();
|
|
if (m_SetPhase) recorder.SetPhase(m_Previous, now);
|
|
recorder.Exit(now);
|
|
recorder.AddMessageTime(m_Key, now - m_StartNs);
|
|
}
|
|
}
|