Ember
Loading...
Searching...
No Matches
PerformanceMonitor.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <fstream>
4#include <iomanip>
5#include <sstream>
6#include <wx/dc.h>
7#include <wx/dcbuffer.h>
8#include <wx/file.h>
9#include <wx/wx.h>
10
11#ifdef __linux__
12#include <fstream>
13#include <sys/sysinfo.h>
14#include <sys/times.h>
15#include <unistd.h>
16#endif
17
18namespace EmberForge {
19
20// MetricData Implementation
21MetricData::MetricData(const wxString &n, const wxColour &c, const wxString &u, size_t max_s)
22 : name(n), color(c), min_value(0.0f), max_value(0.0f), current_value(0.0f), unit(u), max_samples(max_s) {}
23
24void MetricData::AddValue(float value) {
25 current_value = value;
26 values.push_back(value);
27
28 // Update min/max
29 if (values.size() == 1) {
30 min_value = max_value = value;
31 } else {
32 min_value = std::min(min_value, value);
33 max_value = std::max(max_value, value);
34 }
35
36 // Trim to max samples
37 if (values.size() > max_samples) {
38 values.pop_front();
39
40 // Recalculate min/max from remaining values
41 if (!values.empty()) {
42 auto minmax = std::minmax_element(values.begin(), values.end());
43 min_value = *minmax.first;
44 max_value = *minmax.second;
45 }
46 }
47}
48
50 if (values.empty())
51 return 0.0f;
52
53 float sum = 0.0f;
54 for (float value : values) {
55 sum += value;
56 }
57 return sum / values.size();
58}
59
61 values.clear();
63}
64
65// PerformanceMonitor Implementation
67 : m_cpuUsage(0.0f), m_memoryUsageMB(0.0f), m_currentFPS(0.0f), m_enabled(true), m_frameActive(false)
68#ifdef __linux__
69 ,
70 m_lastCpuTotal(0), m_lastCpuIdle(0)
71#endif
72{
74 m_lastFrameTime = std::chrono::high_resolution_clock::now();
75}
76
78 // Cleanup handled automatically
79}
80
82 // Use default 200 samples (20 seconds), but this can be changed later via the UI
83 m_frameTimeMetric = std::make_shared<MetricData>("Frame Time", wxColour(100, 200, 100), "ms", 200);
84 m_fpsMetric = std::make_shared<MetricData>("FPS", wxColour(200, 100, 100), "fps", 200);
85 m_cpuMetric = std::make_shared<MetricData>("CPU Usage", wxColour(100, 100, 200), "%", 200);
86 m_memoryMetric = std::make_shared<MetricData>("App Memory", wxColour(200, 200, 100), "MB", 200);
87}
88
90 if (!m_enabled)
91 return;
92
93 m_frameStart = std::chrono::high_resolution_clock::now();
94 m_frameActive = true;
95}
96
98 if (!m_enabled || !m_frameActive)
99 return;
100
101 auto frameEnd = std::chrono::high_resolution_clock::now();
102
103 // Calculate frame time
104 auto frameTime = std::chrono::duration<float, std::milli>(frameEnd - m_frameStart).count();
105 m_frameTimeMetric->AddValue(frameTime);
106
107 // Update FPS
108 UpdateFPS();
109
110 // Update system metrics periodically
111 static int frameCount = 0;
112 if (++frameCount % 30 == 0) { // Update every 30 frames
114 }
115
116 m_frameActive = false;
117 m_lastFrameTime = frameEnd;
118}
119
121 auto now = std::chrono::high_resolution_clock::now();
122 auto deltaTime = std::chrono::duration<float>(now - m_lastFrameTime).count();
123
124 if (deltaTime > 0.0f) {
125 float fps = 1.0f / deltaTime;
126 m_currentFPS = fps;
127 m_fpsMetric->AddValue(fps);
128 }
129}
130
138
140#ifdef __linux__
141 std::ifstream file("/proc/stat");
142 if (!file.is_open())
143 return 0.0f;
144
145 std::string line;
146 std::getline(file, line);
147
148 std::istringstream iss(line);
149 std::string cpu;
150 unsigned long long user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice;
151
152 iss >> cpu >> user >> nice >> system >> idle >> iowait >> irq >> softirq >> steal >> guest >> guest_nice;
153
154 unsigned long long totalIdle = idle + iowait;
155 unsigned long long totalNonIdle = user + nice + system + irq + softirq + steal;
156 unsigned long long total = totalIdle + totalNonIdle;
157
158 if (m_lastCpuTotal == 0) {
159 m_lastCpuTotal = total;
160 m_lastCpuIdle = totalIdle;
161 return 0.0f;
162 }
163
164 unsigned long long totalDiff = total - m_lastCpuTotal;
165 unsigned long long idleDiff = totalIdle - m_lastCpuIdle;
166
167 float cpuPercent = 0.0f;
168 if (totalDiff > 0) {
169 cpuPercent = ((float)(totalDiff - idleDiff) / totalDiff) * 100.0f;
170 }
171
172 m_lastCpuTotal = total;
173 m_lastCpuIdle = totalIdle;
174
175 return cpuPercent;
176#else
177 return 0.0f; // Placeholder for other platforms
178#endif
179}
180
182#ifdef __linux__
183 // Read application's memory usage from /proc/self/status
184 std::ifstream statusFile("/proc/self/status");
185 if (!statusFile.is_open())
186 return 0.0f;
187
188 std::string line;
189 float vmRSSKb = 0.0f;
190
191 while (std::getline(statusFile, line)) {
192 if (line.substr(0, 6) == "VmRSS:") {
193 // Extract the value (in kB)
194 std::istringstream iss(line);
195 std::string label, unit;
196 iss >> label >> vmRSSKb >> unit;
197 break;
198 }
199 }
200
201 // Convert from kB to MB
202 return vmRSSKb / 1024.0f;
203#else
204 return 0.0f; // Placeholder for other platforms
205#endif
206}
207
208void PerformanceMonitor::RecordMetric(const std::string &name, float value, const std::string &unit) {
209 if (!m_enabled)
210 return;
211
212 auto it = m_customMetrics.find(name);
213 if (it == m_customMetrics.end()) {
214 // Create new metric with a unique color
215 static int colorIndex = 0;
216 wxColour colors[] = {wxColour(255, 100, 100), wxColour(100, 255, 100), wxColour(100, 100, 255),
217 wxColour(255, 255, 100), wxColour(255, 100, 255), wxColour(100, 255, 255),
218 wxColour(200, 150, 100), wxColour(150, 200, 100), wxColour(100, 150, 200)};
219
220 wxColour color = colors[colorIndex % (sizeof(colors) / sizeof(colors[0]))];
221 colorIndex++;
222
223 m_customMetrics[name] = std::make_shared<MetricData>(name, color, unit, 120);
224 }
225
226 m_customMetrics[name]->AddValue(value);
227}
228
229void PerformanceMonitor::RecordTime(const std::string &name, double milliseconds) {
230 RecordMetric(name, static_cast<float>(milliseconds), "ms");
231}
232
233std::shared_ptr<MetricData> PerformanceMonitor::GetMetric(const std::string &name) const {
234 // Check built-in metrics first
235 if (name == "Frame Time")
236 return m_frameTimeMetric;
237 if (name == "FPS")
238 return m_fpsMetric;
239 if (name == "CPU Usage")
240 return m_cpuMetric;
241 if (name == "App Memory")
242 return m_memoryMetric;
243
244 // Check custom metrics
245 auto it = m_customMetrics.find(name);
246 if (it != m_customMetrics.end()) {
247 return it->second;
248 }
249
250 return nullptr;
251}
252
253std::vector<std::shared_ptr<MetricData>> PerformanceMonitor::GetAllMetrics() const {
254 std::vector<std::shared_ptr<MetricData>> metrics;
255
256 // Only add built-in metrics that we have toggles for
257 metrics.push_back(m_frameTimeMetric);
258 metrics.push_back(m_fpsMetric);
259 metrics.push_back(m_cpuMetric);
260 metrics.push_back(m_memoryMetric);
261
262 // Note: Custom metrics are ignored to keep the performance tab clean
263 // Only showing metrics with explicit toggle controls
264
265 return metrics;
266}
267
269 m_frameTimeMetric->Clear();
270 m_fpsMetric->Clear();
271 m_cpuMetric->Clear();
272 m_memoryMetric->Clear();
273
274 for (auto &pair : m_customMetrics) {
275 pair.second->Clear();
276 }
277}
278
280 static PerformanceMonitor instance;
281 return instance;
282}
283
284// PerformancePanel Implementation
286 EVT_PAINT(PerformancePanel::OnPaint) EVT_SIZE(PerformancePanel::OnSize)
287 EVT_BUTTON(ID_TOGGLE_MONITORING, PerformancePanel::OnToggleMonitoring)
288 EVT_BUTTON(ID_CLEAR_METRICS, PerformancePanel::OnClearMetrics)
289 EVT_BUTTON(ID_EXPORT_METRICS, PerformancePanel::OnExportMetrics)
290 EVT_CHECKBOX(ID_TOGGLE_FRAME_TIME, PerformancePanel::OnToggleChart)
291 EVT_CHECKBOX(ID_TOGGLE_FPS, PerformancePanel::OnToggleChart)
292 EVT_CHECKBOX(ID_TOGGLE_CPU, PerformancePanel::OnToggleChart)
293 EVT_CHECKBOX(ID_TOGGLE_MEMORY, PerformancePanel::OnToggleChart)
294 EVT_BUTTON(ID_HISTORY_MINUS, PerformancePanel::OnHistoryMinus)
295 EVT_BUTTON(ID_HISTORY_PLUS, PerformancePanel::OnHistoryPlus) wxEND_EVENT_TABLE()
296
297 PerformancePanel::PerformancePanel(wxWindow *parent,
298 PerformanceMonitor *monitor)
299 : wxPanel(parent, wxID_ANY), m_monitor(monitor ? monitor : &PerformanceMonitor::GetInstance()),
300 m_updateTimer(nullptr), m_monitoring(false),
301 m_chartHistory(200) // Default 20 seconds (200 samples at 10 samples/sec)
302 ,
303 m_chartHeight(100), m_chartMargin(10) {
304 SetBackgroundColour(wxColour(40, 40, 40));
305 SetBackgroundStyle(wxBG_STYLE_PAINT);
306
307 // Set up fonts
308 m_labelFont = wxFont(8, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
309 m_valueFont = wxFont(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
310
311 // Initialize chart enabled states - all disabled by default for clean start
312 m_chartEnabled["Frame Time"] = false;
313 m_chartEnabled["FPS"] = false;
314 m_chartEnabled["CPU Usage"] = false;
315 m_chartEnabled["App Memory"] = false;
316
317 CreateLayout();
318
319 // Set monitor to match initial state (disabled) and clear any existing data
320 if (m_monitor) {
321 m_monitor->SetEnabled(m_monitoring);
322 m_monitor->Clear(); // Start with clean metrics
323 }
324
325 // Set up update timer
326 m_updateTimer = new wxTimer(this, ID_UPDATE_TIMER);
327 m_updateTimer->Start(100); // Update every 100ms
328}
329
331 if (m_updateTimer) {
332 m_updateTimer->Stop();
333 delete m_updateTimer;
334 }
335}
336
340
342 wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
343
344 // Set the sizer first before creating child components
345 SetSizer(mainSizer);
346
348 CreateCharts();
349}
350
352 wxPanel *toolbar = new wxPanel(this, wxID_ANY);
353 toolbar->SetBackgroundColour(wxColour(50, 50, 50));
354
355 wxBoxSizer *toolbarSizer = new wxBoxSizer(wxHORIZONTAL);
356
357 // Toggle monitoring button
358 m_toggleButton = new wxButton(toolbar, ID_TOGGLE_MONITORING, "START | STOP");
359 toolbarSizer->Add(m_toggleButton, 0, wxALL, 5);
360
361 // Clear metrics button
362 m_clearButton = new wxButton(toolbar, ID_CLEAR_METRICS, "Clear");
363 toolbarSizer->Add(m_clearButton, 0, wxALL, 5);
364
365 // Export button
366 m_exportButton = new wxButton(toolbar, ID_EXPORT_METRICS, "Export");
367 toolbarSizer->Add(m_exportButton, 0, wxALL, 5);
368
369 // Add separator
370 toolbarSizer->AddSpacer(20);
371
372 // Chart toggle checkboxes
373 wxStaticText *chartsLabel = new wxStaticText(toolbar, wxID_ANY, "Charts:");
374 chartsLabel->SetForegroundColour(wxColour(200, 200, 200));
375 toolbarSizer->Add(chartsLabel, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
376
377 m_frameTimeCheckbox = new wxCheckBox(toolbar, ID_TOGGLE_FRAME_TIME, "Frame Time");
378 m_frameTimeCheckbox->SetValue(false);
379 m_frameTimeCheckbox->SetForegroundColour(wxColour(200, 200, 200));
380 toolbarSizer->Add(m_frameTimeCheckbox, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
381
382 m_fpsCheckbox = new wxCheckBox(toolbar, ID_TOGGLE_FPS, "FPS");
383 m_fpsCheckbox->SetValue(false);
384 m_fpsCheckbox->SetForegroundColour(wxColour(200, 200, 200));
385 toolbarSizer->Add(m_fpsCheckbox, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
386
387 m_cpuCheckbox = new wxCheckBox(toolbar, ID_TOGGLE_CPU, "CPU");
388 m_cpuCheckbox->SetValue(false);
389 m_cpuCheckbox->SetForegroundColour(wxColour(200, 200, 200));
390 toolbarSizer->Add(m_cpuCheckbox, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
391
392 m_memoryCheckbox = new wxCheckBox(toolbar, ID_TOGGLE_MEMORY, "Memory");
393 m_memoryCheckbox->SetValue(false);
394 m_memoryCheckbox->SetForegroundColour(wxColour(200, 200, 200));
395 toolbarSizer->Add(m_memoryCheckbox, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
396
397 toolbarSizer->AddStretchSpacer();
398
399 // Add separator
400 toolbarSizer->AddSpacer(20);
401
402 // Timeframe control
403 wxStaticText *timeframeLabel = new wxStaticText(toolbar, wxID_ANY, "History:");
404 timeframeLabel->SetForegroundColour(wxColour(200, 200, 200));
405 toolbarSizer->Add(timeframeLabel, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8);
406
407 m_historyMinusButton = new wxButton(toolbar, ID_HISTORY_MINUS, "-", wxDefaultPosition, wxSize(35, 28));
408 wxFont minusFont = m_historyMinusButton->GetFont();
409 minusFont.SetPointSize(14);
410 minusFont.SetWeight(wxFONTWEIGHT_BOLD);
411 m_historyMinusButton->SetFont(minusFont);
412 toolbarSizer->Add(m_historyMinusButton, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
413
414 m_historyText = new wxStaticText(toolbar, wxID_ANY, wxString::Format("%d", m_chartHistory / 10), wxDefaultPosition,
415 wxDefaultSize, wxALIGN_CENTRE);
416 m_historyText->SetForegroundColour(wxColour(200, 200, 200));
417 m_historyText->SetMinSize(wxSize(40, -1));
418 toolbarSizer->Add(m_historyText, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL, 0);
419
420 m_historyPlusButton = new wxButton(toolbar, ID_HISTORY_PLUS, "+", wxDefaultPosition, wxSize(35, 28));
421 wxFont plusFont = m_historyPlusButton->GetFont();
422 plusFont.SetPointSize(14);
423 plusFont.SetWeight(wxFONTWEIGHT_BOLD);
424 m_historyPlusButton->SetFont(plusFont);
425 toolbarSizer->Add(m_historyPlusButton, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
426
427 wxStaticText *secondsLabel = new wxStaticText(toolbar, wxID_ANY, "seconds");
428 secondsLabel->SetForegroundColour(wxColour(200, 200, 200));
429 toolbarSizer->Add(secondsLabel, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 8);
430
431 toolbarSizer->AddStretchSpacer();
432
433 // Status text
434 m_statusText = new wxStaticText(toolbar, wxID_ANY, "OFF");
435 m_statusText->SetForegroundColour(wxColour(255, 0, 0)); // Red for inactive
436 toolbarSizer->Add(m_statusText, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
437
438 toolbar->SetSizer(toolbarSizer);
439
440 // Safely add to main sizer if it exists
441 wxSizer *mainSizer = GetSizer();
442 if (mainSizer) {
443 mainSizer->Add(toolbar, 0, wxEXPAND | wxALL, 2);
444 }
445}
446
448 m_chartsPanel = new wxPanel(this, wxID_ANY);
449 m_chartsPanel->SetBackgroundColour(wxColour(30, 30, 30));
450 m_chartsPanel->SetBackgroundStyle(wxBG_STYLE_PAINT);
451 m_chartsPanel->Bind(wxEVT_PAINT, &PerformancePanel::OnPaint, this);
452 m_chartsPanel->Bind(wxEVT_SIZE, &PerformancePanel::OnSize, this);
453
454 // Safely add to main sizer if it exists
455 wxSizer *mainSizer = GetSizer();
456 if (mainSizer) {
457 mainSizer->Add(m_chartsPanel, 1, wxEXPAND | wxALL, 2);
458 }
459}
460
461void PerformancePanel::OnUpdateTimer(wxTimerEvent &event) {
462 if (m_monitoring && m_monitor) {
464 }
465}
466
471
473 if (!m_monitor)
474 return;
475
476 // Status text is now handled by the toggle button to show ON/OFF state
477 // Detailed metrics are visible in the individual charts
478
479 // Keep the status text as simple ON/OFF indicator
480 // m_statusText shows monitoring state, not detailed metrics
481}
482
483void PerformancePanel::OnPaint(wxPaintEvent &event) {
484 if (!m_monitor)
485 return;
486
487 // Get the window that received the paint event
488 wxWindow *paintWindow = dynamic_cast<wxWindow *>(event.GetEventObject());
489 if (!paintWindow)
490 paintWindow = m_chartsPanel;
491 wxAutoBufferedPaintDC dc(paintWindow);
492 dc.Clear();
493
494 auto allMetrics = m_monitor->GetAllMetrics();
495 if (allMetrics.empty())
496 return;
497
498 // Filter enabled charts only - also verify metrics are valid
499 std::vector<std::shared_ptr<MetricData>> enabledMetrics;
500 for (const auto &metric : allMetrics) {
501 if (metric && !metric->name.empty() && IsChartEnabled(metric->name.ToStdString())) {
502 enabledMetrics.push_back(metric);
503 }
504 }
505
506 if (enabledMetrics.empty())
507 return;
508
509 int numCharts = enabledMetrics.size();
510 for (int i = 0; i < numCharts; ++i) {
511 wxRect chartRect = GetChartRect(i, numCharts);
512 DrawChart(dc, chartRect, enabledMetrics[i]);
513 }
514}
515
516void PerformancePanel::DrawChart(wxDC &dc, const wxRect &rect, std::shared_ptr<MetricData> metric) {
517 if (!metric || metric->values.empty())
518 return;
519
520 // Draw background
521 dc.SetBrush(wxBrush(wxColour(25, 25, 25)));
522 dc.SetPen(wxPen(wxColour(60, 60, 60)));
523 dc.DrawRectangle(rect);
524
525 // Draw grid
526 DrawGrid(dc, rect, metric->min_value, metric->max_value);
527
528 // Draw metric info
529 wxRect infoRect = rect;
530 infoRect.height = 25;
531 DrawMetricInfo(dc, infoRect, metric);
532
533 // Draw chart data
534 if (metric->values.size() < 2)
535 return;
536
537 wxRect dataRect = rect;
538 dataRect.y += 25;
539 dataRect.height -= 25;
540
541 float range = metric->max_value - metric->min_value;
542 if (range <= 0.0f)
543 range = 1.0f;
544
545 // Draw line chart
546 dc.SetPen(wxPen(metric->color, 2));
547
548 const auto &values = metric->values;
549 int numValues = values.size();
550 float xStep = (float)dataRect.width / (numValues - 1);
551
552 for (int i = 1; i < numValues; ++i) {
553 float x1 = dataRect.x + (i - 1) * xStep;
554 float y1 = dataRect.y + dataRect.height - ((values[i - 1] - metric->min_value) / range) * dataRect.height;
555 float x2 = dataRect.x + i * xStep;
556 float y2 = dataRect.y + dataRect.height - ((values[i] - metric->min_value) / range) * dataRect.height;
557
558 dc.DrawLine(wxPoint(x1, y1), wxPoint(x2, y2));
559 }
560}
561
562void PerformancePanel::DrawMetricInfo(wxDC &dc, const wxRect &rect, std::shared_ptr<MetricData> metric) {
563 dc.SetFont(m_labelFont);
564 dc.SetTextForeground(wxColour(200, 200, 200));
565
566 wxString info = wxString::Format(
567 "%s: %s (Avg: %s, Min: %s, Max: %s)", metric->name, FormatValue(metric->current_value, metric->unit),
568 FormatValue(metric->GetAverage(), metric->unit), FormatValue(metric->min_value, metric->unit),
569 FormatValue(metric->max_value, metric->unit));
570
571 dc.DrawText(info, rect.x + 5, rect.y + 5);
572}
573
574void PerformancePanel::DrawGrid(wxDC &dc, const wxRect &rect, float minVal, float maxVal) {
575 dc.SetPen(wxPen(wxColour(50, 50, 50), 1));
576
577 // Horizontal grid lines
578 for (int i = 1; i < 5; ++i) {
579 int y = rect.y + (rect.height * i) / 5;
580 dc.DrawLine(rect.x, y, rect.x + rect.width, y);
581 }
582
583 // Vertical grid lines
584 for (int i = 1; i < 10; ++i) {
585 int x = rect.x + (rect.width * i) / 10;
586 dc.DrawLine(x, rect.y, x, rect.y + rect.height);
587 }
588}
589
590wxRect PerformancePanel::GetChartRect(int index, int totalCharts) const {
591 wxSize size = m_chartsPanel->GetSize();
592
593 // Prevent division by zero and ensure positive dimensions
594 if (totalCharts <= 0)
595 totalCharts = 1;
596
597 int availableHeight = size.y - (totalCharts + 1) * m_chartMargin;
598 int chartHeight = std::max(50, availableHeight / totalCharts); // Minimum 50px height
599 int y = m_chartMargin + index * (chartHeight + m_chartMargin);
600
601 int chartWidth = std::max(100, size.x - 2 * m_chartMargin); // Minimum 100px width
602
603 return wxRect(m_chartMargin, y, chartWidth, chartHeight);
604}
605
606wxString PerformancePanel::FormatValue(float value, const wxString &unit) const {
607 if (unit == "ms") {
608 return wxString::Format("%.2fms", value);
609 } else if (unit == "fps") {
610 return wxString::Format("%.1f", value);
611 } else if (unit == "%") {
612 return wxString::Format("%.1f%%", value);
613 } else if (unit == "MB") {
614 return wxString::Format("%.1fMB", value);
615 } else {
616 return wxString::Format("%.2f", value) + (unit.IsEmpty() ? "" : unit);
617 }
618}
619
620void PerformancePanel::OnSize(wxSizeEvent &event) {
621 m_chartsPanel->Refresh();
622 event.Skip();
623}
624
625void PerformancePanel::OnToggleMonitoring(wxCommandEvent &event) {
627
628 if (m_monitor) {
629 m_monitor->SetEnabled(m_monitoring);
630 }
631
632 // Update status text with color
633 if (m_monitoring) {
634 m_statusText->SetLabel("ON");
635 m_statusText->SetForegroundColour(wxColour(0, 255, 0)); // Green for active
636 } else {
637 m_statusText->SetLabel("OFF");
638 m_statusText->SetForegroundColour(wxColour(255, 0, 0)); // Red for stopped
639 }
640
641 // Refresh the toolbar to show color changes
642 m_statusText->Refresh();
643}
644
645void PerformancePanel::OnClearMetrics(wxCommandEvent &event) {
646 if (m_monitor) {
647 m_monitor->Clear();
649 }
650}
651
652void PerformancePanel::OnExportMetrics(wxCommandEvent &event) {
653 if (!m_monitor)
654 return;
655
656 wxFileDialog saveDialog(this, "Export Performance Data", "", "performance.csv",
657 "CSV files (*.csv)|*.csv|All files (*.*)|*.*", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
658
659 if (saveDialog.ShowModal() == wxID_OK) {
660 wxString path = saveDialog.GetPath();
661
662 auto metrics = m_monitor->GetAllMetrics();
663 if (metrics.empty())
664 return;
665
666 // Find maximum number of samples
667 size_t maxSamples = 0;
668 for (const auto &metric : metrics) {
669 maxSamples = std::max(maxSamples, metric->values.size());
670 }
671
672 wxString content = "Sample";
673 for (const auto &metric : metrics) {
674 content += ",%s (" + metric->name, metric->unit + ")";
675 }
676 content += "\n";
677
678 for (size_t i = 0; i < maxSamples; ++i) {
679 content += wxString::Format("%zu", i);
680 for (const auto &metric : metrics) {
681 if (i < metric->values.size()) {
682 content += wxString::Format(",%.3f", metric->values[i]);
683 } else {
684 content += ",";
685 }
686 }
687 content += "\n";
688 }
689
690 wxFile file(path, wxFile::write);
691 if (file.IsOpened()) {
692 file.Write(content);
693 file.Close();
694
695 wxMessageBox("Performance data exported to " + path + "", "Export Complete", wxOK | wxICON_INFORMATION,
696 this);
697 } else {
698 wxMessageBox("Failed to save performance data", "Export Error", wxOK | wxICON_ERROR, this);
699 }
700 }
701}
702
704 if (m_updateTimer) {
705 m_updateTimer->Stop();
706 m_updateTimer->Start(milliseconds);
707 }
708}
709
710void PerformancePanel::OnToggleChart(wxCommandEvent &event) {
711 int id = event.GetId();
712 bool enabled = static_cast<wxCheckBox *>(event.GetEventObject())->GetValue();
713
714 switch (id) {
716 SetChartEnabled("Frame Time", enabled);
717 break;
718 case ID_TOGGLE_FPS:
719 SetChartEnabled("FPS", enabled);
720 break;
721 case ID_TOGGLE_CPU:
722 SetChartEnabled("CPU Usage", enabled);
723 break;
724 case ID_TOGGLE_MEMORY:
725 SetChartEnabled("App Memory", enabled);
726 break;
727 }
728
730}
731
732void PerformancePanel::SetChartEnabled(const std::string &chartName, bool enabled) {
733 m_chartEnabled[chartName] = enabled;
734}
735
736bool PerformancePanel::IsChartEnabled(const std::string &chartName) const {
737 // Only allow the 4 built-in charts we have toggles for
738 if (chartName != "Frame Time" && chartName != "FPS" && chartName != "CPU Usage" && chartName != "App Memory") {
739 return false; // Unknown charts are disabled
740 }
741
742 auto it = m_chartEnabled.find(chartName);
743 return it != m_chartEnabled.end() ? it->second : false; // Default to false for clean start
744}
745
747 for (auto &pair : m_chartEnabled) {
748 pair.second = enabled;
749 }
750
751 // Update checkbox states
752 m_frameTimeCheckbox->SetValue(enabled);
753 m_fpsCheckbox->SetValue(enabled);
754 m_cpuCheckbox->SetValue(enabled);
755 m_memoryCheckbox->SetValue(enabled);
756
758}
759
760std::vector<std::string> PerformancePanel::GetAvailableCharts() const {
761 std::vector<std::string> charts;
762 for (const auto &pair : m_chartEnabled) {
763 charts.push_back(pair.first);
764 }
765 return charts;
766}
767
768void PerformancePanel::OnHistoryMinus(wxCommandEvent &event) {
769 int currentSeconds = m_chartHistory / 10;
770 if (currentSeconds >= 20) { // Minimum 10 seconds (allow decrease from 20 to 10)
771 currentSeconds -= 10;
772 m_chartHistory = currentSeconds * 10;
773
774 // Update display
775 m_historyText->SetLabel(wxString::Format("%d", currentSeconds));
776
777 // Update the max_samples for all metrics
778 if (m_monitor) {
779 auto metrics = m_monitor->GetAllMetrics();
780 for (auto &metric : metrics) {
781 if (metric) {
782 metric->max_samples = m_chartHistory;
783
784 // Trim existing values if necessary
785 while (metric->values.size() > static_cast<size_t>(m_chartHistory)) {
786 metric->values.pop_front();
787 }
788 }
789 }
790 }
791
793 }
794}
795
796void PerformancePanel::OnHistoryPlus(wxCommandEvent &event) {
797 int currentSeconds = m_chartHistory / 10;
798 if (currentSeconds <= 990) { // Maximum 1000 seconds (ensure we don't exceed when adding 10)
799 currentSeconds += 10;
800 m_chartHistory = currentSeconds * 10;
801
802 // Update display
803 m_historyText->SetLabel(wxString::Format("%d", currentSeconds));
804
805 // Update the max_samples for all metrics
806 if (m_monitor) {
807 auto metrics = m_monitor->GetAllMetrics();
808 for (auto &metric : metrics) {
809 if (metric) {
810 metric->max_samples = m_chartHistory;
811 }
812 }
813 }
814
816 }
817}
818
819} // namespace EmberForge
BehaviorTreeProjectDialog::OnProjectNameChanged BehaviorTreeProjectDialog::OnRemoveFiles wxEND_EVENT_TABLE() BehaviorTreeProjectDialog
System performance monitoring class.
std::shared_ptr< MetricData > m_cpuMetric
std::chrono::high_resolution_clock::time_point m_lastFrameTime
std::shared_ptr< MetricData > GetMetric(const std::string &name) const
void RecordTime(const std::string &name, double milliseconds)
std::shared_ptr< MetricData > m_fpsMetric
std::shared_ptr< MetricData > m_frameTimeMetric
std::shared_ptr< MetricData > m_memoryMetric
void RecordMetric(const std::string &name, float value, const std::string &unit="")
static PerformanceMonitor & GetInstance()
std::chrono::high_resolution_clock::time_point m_frameStart
std::vector< std::shared_ptr< MetricData > > GetAllMetrics() const
std::map< std::string, std::shared_ptr< MetricData > > m_customMetrics
Performance panel UI for displaying real-time metrics.
void DrawChart(wxDC &dc, const wxRect &rect, std::shared_ptr< MetricData > metric)
wxString FormatValue(float value, const wxString &unit) const
void SetChartEnabled(const std::string &chartName, bool enabled)
void DrawGrid(wxDC &dc, const wxRect &rect, float minVal, float maxVal)
void OnPaint(wxPaintEvent &event)
void OnToggleChart(wxCommandEvent &event)
void OnHistoryPlus(wxCommandEvent &event)
std::map< std::string, bool > m_chartEnabled
void OnHistoryMinus(wxCommandEvent &event)
wxRect GetChartRect(int index, int totalCharts) const
void DrawMetricInfo(wxDC &dc, const wxRect &rect, std::shared_ptr< MetricData > metric)
void SetMonitor(PerformanceMonitor *monitor)
void SetUpdateInterval(int milliseconds)
void OnUpdateTimer(wxTimerEvent &event)
bool IsChartEnabled(const std::string &chartName) const
void OnClearMetrics(wxCommandEvent &event)
void OnSize(wxSizeEvent &event)
void OnExportMetrics(wxCommandEvent &event)
std::vector< std::string > GetAvailableCharts() const
void OnToggleMonitoring(wxCommandEvent &event)
LogTab::OnLevelFilterChanged LogTab::OnCategoryFilterChanged LogTab::OnAutoScrollToggled LogTab::OnConsoleToggled EVT_CHECKBOX(ID_FILE_CHECK, LogTab::OnFileToggled) EVT_BUTTON(ID_CLEAR_BTN
wxBEGIN_EVENT_TABLE(LogTab, wxPanel) EVT_CHOICE(ID_LEVEL_FILTER
PerformancePanel::OnUpdateTimer EVT_PAINT(PerformancePanel::OnPaint) EVT_SIZE(PerformancePanel
LogTab::OnLevelFilterChanged LogTab::OnCategoryFilterChanged LogTab::OnAutoScrollToggled EVT_BUTTON(ID_PAUSE_BTN, LogTab::OnPauseToggled) EVT_CHECKBOX(ID_CONSOLE_CHECK
MetricData(const wxString &n, const wxColour &c, const wxString &u="", size_t max_s=120)
std::deque< float > values