Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
PerformanceProfiler.cpp
Go to the documentation of this file.
1/**
2 * @file PerformanceProfiler.cpp
3 * @brief Implementation of the ATS VS performance profiler (Phase 5).
4 * @author Olympe Engine
5 * @date 2026-03-09
6 *
7 * @details C++14 compliant — no std::optional, structured bindings, std::filesystem.
8 */
9
10#include "PerformanceProfiler.h"
11
12#include <fstream>
13#include <algorithm>
14#include <iostream>
15
16namespace Olympe {
17
18// ============================================================================
19// Singleton
20// ============================================================================
21
27
31
35
36// ============================================================================
37// Profiling lifecycle
38// ============================================================================
39
41{
42 std::lock_guard<std::mutex> lock(m_mutex);
43 m_profiling = true;
44 m_frameNumber = 0;
45 m_frameNodeMetrics.clear();
46 m_nodeStartTimes.clear();
47 m_frameHistory.clear();
48 // Keep lifetime metrics — they accumulate across sessions
49}
50
52{
53 std::lock_guard<std::mutex> lock(m_mutex);
54 m_profiling = false;
55 m_nodeStartTimes.clear();
56 m_frameNodeMetrics.clear();
57}
58
60{
61 std::lock_guard<std::mutex> lock(m_mutex);
62 return m_profiling;
63}
64
65// ============================================================================
66// Frame lifecycle
67// ============================================================================
68
70{
71 std::lock_guard<std::mutex> lock(m_mutex);
72 if (!m_profiling)
73 return;
74
76 m_frameNodeMetrics.clear();
77 m_nodeStartTimes.clear();
78 m_frameStartTime = std::chrono::steady_clock::now();
79}
80
82{
83 std::lock_guard<std::mutex> lock(m_mutex);
84 if (!m_profiling)
85 return;
86
87 auto frameEnd = std::chrono::steady_clock::now();
88 float totalMs = std::chrono::duration<float, std::milli>(
89 frameEnd - m_frameStartTime).count();
90
91 FrameProfile frame;
94
95 for (auto& kv : m_frameNodeMetrics)
96 {
97 frame.nodeMetrics.push_back(kv.second);
98 }
99
100 // Sort by execution time descending
101 std::sort(frame.nodeMetrics.begin(), frame.nodeMetrics.end(),
102 [](const NodeExecutionMetrics& a, const NodeExecutionMetrics& b) {
103 return a.executionTimeMs > b.executionTimeMs;
104 });
105
106 // Add to rolling history
107 m_frameHistory.push_back(frame);
108 if (static_cast<int>(m_frameHistory.size()) > MAX_FRAME_HISTORY)
109 {
110 m_frameHistory.erase(m_frameHistory.begin());
111 }
112}
113
114// ============================================================================
115// Node instrumentation
116// ============================================================================
117
119 const std::string& nodeName)
120{
121 std::lock_guard<std::mutex> lock(m_mutex);
122 if (!m_profiling)
123 return;
124
125 m_nodeStartTimes[nodeID] = std::chrono::steady_clock::now();
126
127 // Ensure frame metric entry exists with node name
128 if (m_frameNodeMetrics.find(nodeID) == m_frameNodeMetrics.end())
129 {
131 m.nodeID = nodeID;
132 m.nodeName = nodeName;
133 m_frameNodeMetrics[nodeID] = m;
134 }
135}
136
138{
139 std::lock_guard<std::mutex> lock(m_mutex);
140 if (!m_profiling)
141 return;
142
143 auto startIt = m_nodeStartTimes.find(nodeID);
144 if (startIt == m_nodeStartTimes.end())
145 return;
146
147 auto now = std::chrono::steady_clock::now();
148 float elapsedMs = std::chrono::duration<float, std::milli>(
149 now - startIt->second).count();
150
152
153 // Update per-frame metrics
154 auto& fMetric = m_frameNodeMetrics[nodeID];
155 fMetric.nodeID = nodeID;
156 fMetric.executionTimeMs = elapsedMs;
157 fMetric.executionCount += 1;
158 fMetric.totalTimeMs += elapsedMs;
159 fMetric.avgTimeMs = fMetric.totalTimeMs / static_cast<float>(fMetric.executionCount);
160 if (elapsedMs > fMetric.maxTimeMs)
161 fMetric.maxTimeMs = elapsedMs;
162
163 // Update lifetime metrics
164 auto& lMetric = m_lifetimeMetrics[nodeID];
165 if (lMetric.nodeID == -1 || lMetric.nodeName.empty())
166 lMetric.nodeName = fMetric.nodeName;
167 lMetric.nodeID = nodeID;
168 lMetric.executionTimeMs = elapsedMs;
169 lMetric.executionCount += 1;
170 lMetric.totalTimeMs += elapsedMs;
171 lMetric.avgTimeMs = lMetric.totalTimeMs / static_cast<float>(lMetric.executionCount);
172 if (elapsedMs > lMetric.maxTimeMs)
173 lMetric.maxTimeMs = elapsedMs;
174}
175
176// ============================================================================
177// Data access
178// ============================================================================
179
181{
182 std::lock_guard<std::mutex> lock(m_mutex);
183 FrameProfile frame;
185 frame.totalFrameTimeMs = 0.0f;
186 for (auto& kv : m_frameNodeMetrics)
187 frame.nodeMetrics.push_back(kv.second);
188 return frame;
189}
190
191std::vector<FrameProfile> PerformanceProfiler::GetFrameHistory(int count) const
192{
193 std::lock_guard<std::mutex> lock(m_mutex);
194 if (count <= 0 || count >= static_cast<int>(m_frameHistory.size()))
195 return m_frameHistory;
196
197 // Return last `count` frames
198 auto start = m_frameHistory.end() - count;
199 return std::vector<FrameProfile>(start, m_frameHistory.end());
200}
201
202std::vector<NodeExecutionMetrics> PerformanceProfiler::GetHotspots() const
203{
204 std::lock_guard<std::mutex> lock(m_mutex);
205 std::vector<NodeExecutionMetrics> result;
206 for (auto& kv : m_lifetimeMetrics)
207 result.push_back(kv.second);
208
209 std::sort(result.begin(), result.end(),
210 [](const NodeExecutionMetrics& a, const NodeExecutionMetrics& b) {
211 return a.avgTimeMs > b.avgTimeMs;
212 });
213 return result;
214}
215
217{
218 std::lock_guard<std::mutex> lock(m_mutex);
219 m_frameNodeMetrics.clear();
220 m_nodeStartTimes.clear();
221 m_lifetimeMetrics.clear();
222 m_frameHistory.clear();
223 m_frameNumber = 0;
224}
225
226bool PerformanceProfiler::SaveToFile(const std::string& path) const
227{
228 std::lock_guard<std::mutex> lock(m_mutex);
229
230 std::ofstream ofs(path);
231 if (!ofs.is_open())
232 {
233 std::cerr << "[PerformanceProfiler] Failed to open file: " << path << std::endl;
234 return false;
235 }
236
237 // CSV header
238 ofs << "FrameNumber,TotalFrameTimeMs,NodeID,NodeName,ExecTimeMs,"
239 "AvgTimeMs,MaxTimeMs,ExecCount\n";
240
241 for (size_t f = 0; f < m_frameHistory.size(); ++f)
242 {
243 const FrameProfile& frame = m_frameHistory[f];
244 for (size_t n = 0; n < frame.nodeMetrics.size(); ++n)
245 {
246 const NodeExecutionMetrics& m = frame.nodeMetrics[n];
247 ofs << frame.frameNumber << ","
248 << frame.totalFrameTimeMs << ","
249 << m.nodeID << ","
250 << m.nodeName << ","
251 << m.executionTimeMs << ","
252 << m.avgTimeMs << ","
253 << m.maxTimeMs << ","
254 << m.executionCount << "\n";
255 }
256 }
257
258 ofs.close();
259 return true;
260}
261
262} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Performance profiler for ATS Visual Scripting node execution (Phase 5).
Singleton performance profiler for VS graph node execution.
static PerformanceProfiler & Get()
Returns the singleton instance (Meyers pattern).
bool IsProfiling() const
Returns true if profiling is currently enabled.
std::unordered_map< int, NodeExecutionMetrics > m_lifetimeMetrics
Lifetime metrics per node.
void BeginNodeExecution(int nodeID, const std::string &nodeName)
Records the start time for a node execution.
FrameProfile GetCurrentFrame() const
Returns the current (in-progress) frame profile.
std::unordered_map< int, std::chrono::steady_clock::time_point > m_nodeStartTimes
Per-frame timing: node ID -> start time.
std::unordered_map< int, NodeExecutionMetrics > m_frameNodeMetrics
Accumulated per-frame node metrics (reset each BeginFrame)
void Clear()
Clears all accumulated data and history.
void BeginProfiling()
Enables profiling.
std::vector< FrameProfile > GetFrameHistory(int count=0) const
Returns up to count most recent completed frame profiles.
std::vector< NodeExecutionMetrics > GetHotspots() const
Returns accumulated lifetime metrics sorted by avgTimeMs descending.
bool SaveToFile(const std::string &path) const
Exports the frame history to a CSV file.
void EndNodeExecution(int nodeID)
Records the end time for a node execution and updates metrics.
std::chrono::steady_clock::time_point m_frameStartTime
Frame start time.
void EndFrame()
Marks the end of the current frame.
void StopProfiling()
Disables profiling and optionally clears accumulated data.
static const int MAX_FRAME_HISTORY
Number of frames kept in the rolling history buffer.
std::vector< FrameProfile > m_frameHistory
Rolling frame history buffer.
void BeginFrame()
Marks the start of a new frame.
< Provides AssetID and INVALID_ASSET_ID
Snapshot of all node metrics for a single frame.
std::vector< NodeExecutionMetrics > nodeMetrics
Accumulated per-node execution statistics.