430 satır
15 KiB
MQL5
430 satır
15 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| PerformanceMetricsCollector.mqh - Per-Component Latency Tracking |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef PERFORMANCE_METRICS_COLLECTOR_MQH
|
|
#define PERFORMANCE_METRICS_COLLECTOR_MQH
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Component latency metric entry |
|
|
//+------------------------------------------------------------------+
|
|
struct SComponentLatencyMetric
|
|
{
|
|
string component_name;
|
|
datetime timestamp;
|
|
ulong latency_us; // Microseconds
|
|
ulong count; // Call count
|
|
bool budget_exceeded; // Whether latency exceeded budget
|
|
string context; // Additional context (e.g., "gate_G1", "trade_BTCUSD")
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Rolling window latency statistics |
|
|
//+------------------------------------------------------------------+
|
|
struct SLatencyStats
|
|
{
|
|
string component_name;
|
|
double avg_latency_us;
|
|
double max_latency_us;
|
|
double min_latency_us;
|
|
double p95_latency_us;
|
|
ulong total_calls;
|
|
ulong budget_violations;
|
|
datetime last_updated;
|
|
|
|
void Reset()
|
|
{
|
|
component_name = "";
|
|
avg_latency_us = 0.0;
|
|
max_latency_us = 0.0;
|
|
min_latency_us = 1e9;
|
|
p95_latency_us = 0.0;
|
|
total_calls = 0;
|
|
budget_violations = 0;
|
|
last_updated = 0;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Performance Metrics Collector Class |
|
|
//+------------------------------------------------------------------+
|
|
class CPerformanceMetricsCollector
|
|
{
|
|
private:
|
|
SComponentLatencyMetric m_metrics[];
|
|
int m_metrics_count;
|
|
int m_max_metrics;
|
|
|
|
SLatencyStats m_stats[];
|
|
int m_stats_count;
|
|
|
|
ulong m_component_budgets[]; // Per-component budgets in us
|
|
|
|
// Rolling window for P95 calculation (last 100 samples per component)
|
|
double m_latency_history[][100];
|
|
int m_history_index[];
|
|
int m_history_count[];
|
|
|
|
public:
|
|
// Constructor
|
|
CPerformanceMetricsCollector(int max_metrics = 1000)
|
|
{
|
|
m_metrics_count = 0;
|
|
m_max_metrics = max_metrics;
|
|
m_stats_count = 0;
|
|
|
|
ArrayResize(m_metrics, 0);
|
|
ArrayResize(m_stats, 0);
|
|
ArrayResize(m_component_budgets, 0);
|
|
ArrayResize(m_latency_history, 0);
|
|
ArrayResize(m_history_index, 0);
|
|
ArrayResize(m_history_count, 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Register a component with its latency budget |
|
|
//+------------------------------------------------------------------+
|
|
void RegisterComponent(string component_name, ulong budget_us)
|
|
{
|
|
int idx = FindOrCreateComponentIndex(component_name);
|
|
if(idx >= 0)
|
|
{
|
|
// Ensure arrays are sized
|
|
if(idx >= ArraySize(m_component_budgets))
|
|
{
|
|
int old_size = ArraySize(m_component_budgets);
|
|
ArrayResize(m_component_budgets, idx + 1);
|
|
ArrayResize(m_latency_history, idx + 1);
|
|
ArrayResize(m_history_index, idx + 1);
|
|
ArrayResize(m_history_count, idx + 1);
|
|
|
|
// Initialize new entries
|
|
for(int i = old_size; i <= idx; i++)
|
|
{
|
|
m_component_budgets[i] = 5000; // Default 5ms
|
|
m_history_index[i] = 0;
|
|
m_history_count[i] = 0;
|
|
}
|
|
}
|
|
|
|
m_component_budgets[idx] = budget_us;
|
|
|
|
// Initialize stats entry
|
|
if(idx >= m_stats_count)
|
|
{
|
|
int old_stats = m_stats_count;
|
|
m_stats_count = idx + 1;
|
|
ArrayResize(m_stats, m_stats_count);
|
|
|
|
for(int i = old_stats; i < m_stats_count; i++)
|
|
m_stats[i].Reset();
|
|
}
|
|
|
|
m_stats[idx].component_name = component_name;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Record a latency measurement |
|
|
//+------------------------------------------------------------------+
|
|
void RecordLatency(string component_name, ulong latency_us, string context = "")
|
|
{
|
|
// Add to metrics log (if not at capacity)
|
|
if(m_metrics_count < m_max_metrics)
|
|
{
|
|
int idx = m_metrics_count;
|
|
ArrayResize(m_metrics, m_metrics_count + 1);
|
|
|
|
m_metrics[idx].component_name = component_name;
|
|
m_metrics[idx].timestamp = TimeCurrent();
|
|
m_metrics[idx].latency_us = latency_us;
|
|
m_metrics[idx].count = 1;
|
|
m_metrics[idx].context = context;
|
|
|
|
int comp_idx = FindOrCreateComponentIndex(component_name);
|
|
if(comp_idx >= 0 && comp_idx < ArraySize(m_component_budgets))
|
|
{
|
|
m_metrics[idx].budget_exceeded = (latency_us > m_component_budgets[comp_idx]);
|
|
}
|
|
else
|
|
{
|
|
m_metrics[idx].budget_exceeded = false;
|
|
}
|
|
|
|
m_metrics_count++;
|
|
}
|
|
|
|
// Update rolling statistics
|
|
UpdateStats(component_name, latency_us);
|
|
|
|
// Update history for P95
|
|
UpdateHistory(component_name, latency_us);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get latency statistics for a component |
|
|
//+------------------------------------------------------------------+
|
|
bool GetComponentStats(string component_name, SLatencyStats &out_stats)
|
|
{
|
|
int idx = FindComponentIndex(component_name);
|
|
if(idx < 0 || idx >= m_stats_count)
|
|
return false;
|
|
|
|
// Calculate P95 from history
|
|
CalculateP95(idx);
|
|
|
|
out_stats = m_stats[idx];
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if component exceeded its budget |
|
|
//+------------------------------------------------------------------+
|
|
bool DidExceedBudget(string component_name, ulong latency_us)
|
|
{
|
|
int idx = FindComponentIndex(component_name);
|
|
if(idx < 0 || idx >= ArraySize(m_component_budgets))
|
|
return false;
|
|
|
|
return latency_us > m_component_budgets[idx];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get budget violation report |
|
|
//+------------------------------------------------------------------+
|
|
string GetBudgetViolationReport()
|
|
{
|
|
string report = "=== Performance Budget Violations ===\n";
|
|
bool has_violations = false;
|
|
|
|
for(int i = 0; i < m_stats_count; i++)
|
|
{
|
|
if(m_stats[i].budget_violations > 0)
|
|
{
|
|
has_violations = true;
|
|
report += StringFormat("%s: %d violations (%.2f%% of calls)\n",
|
|
m_stats[i].component_name,
|
|
(int)m_stats[i].budget_violations,
|
|
100.0 * m_stats[i].budget_violations / MathMax(1, (double)m_stats[i].total_calls));
|
|
}
|
|
}
|
|
|
|
if(!has_violations)
|
|
report += "No budget violations recorded.\n";
|
|
|
|
return report;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get full performance report |
|
|
//+------------------------------------------------------------------+
|
|
string GetFullReport()
|
|
{
|
|
string report = "=== Performance Metrics Report ===\n";
|
|
report += StringFormat("Total components: %d\n", m_stats_count);
|
|
report += StringFormat("Total measurements: %d\n\n", m_metrics_count);
|
|
|
|
for(int i = 0; i < m_stats_count; i++)
|
|
{
|
|
// Calculate P95
|
|
CalculateP95(i);
|
|
|
|
report += StringFormat(
|
|
"%s:\n"
|
|
" Avg: %.2fms | P95: %.2fms | Min: %.2fms | Max: %.2fms\n"
|
|
" Calls: %I64u | Budget violations: %I64u\n\n",
|
|
m_stats[i].component_name,
|
|
m_stats[i].avg_latency_us / 1000.0,
|
|
m_stats[i].p95_latency_us / 1000.0,
|
|
m_stats[i].min_latency_us / 1000.0,
|
|
m_stats[i].max_latency_us / 1000.0,
|
|
m_stats[i].total_calls,
|
|
m_stats[i].budget_violations
|
|
);
|
|
}
|
|
|
|
return report;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clear old metrics (keep last N measurements per component) |
|
|
//+------------------------------------------------------------------+
|
|
void TrimOldMetrics(int keep_per_component = 100)
|
|
{
|
|
// Simple implementation: if we exceed max, clear half
|
|
if(m_metrics_count >= m_max_metrics)
|
|
{
|
|
int new_count = m_metrics_count / 2;
|
|
ArrayResize(m_metrics, new_count);
|
|
m_metrics_count = new_count;
|
|
}
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Find or create component index |
|
|
//+------------------------------------------------------------------+
|
|
int FindOrCreateComponentIndex(string component_name)
|
|
{
|
|
int idx = FindComponentIndex(component_name);
|
|
if(idx >= 0)
|
|
return idx;
|
|
|
|
// Create new entry
|
|
idx = m_stats_count;
|
|
m_stats_count++;
|
|
ArrayResize(m_stats, m_stats_count);
|
|
m_stats[idx].Reset();
|
|
m_stats[idx].component_name = component_name;
|
|
|
|
return idx;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Find component index |
|
|
//+------------------------------------------------------------------+
|
|
int FindComponentIndex(string component_name)
|
|
{
|
|
for(int i = 0; i < m_stats_count; i++)
|
|
{
|
|
if(m_stats[i].component_name == component_name)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update statistics for a component |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateStats(string component_name, ulong latency_us)
|
|
{
|
|
int idx = FindOrCreateComponentIndex(component_name);
|
|
if(idx < 0 || idx >= m_stats_count)
|
|
return;
|
|
|
|
SLatencyStats &stats = m_stats[idx];
|
|
|
|
// Update running average
|
|
double old_avg = stats.avg_latency_us;
|
|
ulong n = stats.total_calls;
|
|
stats.avg_latency_us = (old_avg * n + latency_us) / (n + 1);
|
|
|
|
// Update min/max
|
|
stats.max_latency_us = MathMax(stats.max_latency_us, latency_us);
|
|
stats.min_latency_us = MathMin(stats.min_latency_us, latency_us);
|
|
|
|
// Increment call count
|
|
stats.total_calls++;
|
|
|
|
// Check budget violation
|
|
if(idx < ArraySize(m_component_budgets) && latency_us > m_component_budgets[idx])
|
|
stats.budget_violations++;
|
|
|
|
stats.last_updated = TimeCurrent();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update latency history for P95 calculation |
|
|
//+------------------------------------------------------------------+
|
|
void UpdateHistory(string component_name, ulong latency_us)
|
|
{
|
|
int idx = FindOrCreateComponentIndex(component_name);
|
|
if(idx < 0 || idx >= ArraySize(m_history_index))
|
|
return;
|
|
|
|
// Store in circular buffer
|
|
int h_idx = m_history_index[idx];
|
|
m_latency_history[idx][h_idx] = (double)latency_us;
|
|
m_history_index[idx] = (h_idx + 1) % 100;
|
|
|
|
if(m_history_count[idx] < 100)
|
|
m_history_count[idx]++;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate P95 for a component |
|
|
//+------------------------------------------------------------------+
|
|
void CalculateP95(int component_idx)
|
|
{
|
|
if(component_idx < 0 || component_idx >= ArraySize(m_history_count))
|
|
return;
|
|
|
|
int count = m_history_count[component_idx];
|
|
if(count == 0)
|
|
{
|
|
m_stats[component_idx].p95_latency_us = 0.0;
|
|
return;
|
|
}
|
|
|
|
// Copy and sort
|
|
double sorted[];
|
|
ArrayResize(sorted, count);
|
|
ArrayCopy(sorted, m_latency_history[component_idx], 0, 0, count);
|
|
ArraySort(sorted);
|
|
|
|
// Get P95 index
|
|
int p95_idx = (int)MathFloor(count * 0.95);
|
|
if(p95_idx >= count) p95_idx = count - 1;
|
|
|
|
m_stats[component_idx].p95_latency_us = sorted[p95_idx];
|
|
}
|
|
};
|
|
|
|
// Global instance
|
|
CPerformanceMetricsCollector* g_performance_collector = NULL;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize performance collector |
|
|
//+------------------------------------------------------------------+
|
|
bool InitializePerformanceCollector()
|
|
{
|
|
if(g_performance_collector != NULL)
|
|
delete g_performance_collector;
|
|
|
|
g_performance_collector = new CPerformanceMetricsCollector(10000);
|
|
|
|
// Register standard components with budgets
|
|
g_performance_collector.RegisterComponent("OnTick", 5000); // 5ms
|
|
g_performance_collector.RegisterComponent("OnTimer", 10000); // 10ms
|
|
g_performance_collector.RegisterComponent("GateEvaluation", 2000); // 2ms per gate
|
|
g_performance_collector.RegisterComponent("TradeExecution", 5000); // 5ms
|
|
g_performance_collector.RegisterComponent("PolicyUpdate", 5000); // 5ms
|
|
g_performance_collector.RegisterComponent("FeatureCache", 1000); // 1ms
|
|
g_performance_collector.RegisterComponent("PositionSync", 3000); // 3ms
|
|
|
|
LOG("[PerformanceCollector] Initialized with component budgets");
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Shutdown performance collector |
|
|
//+------------------------------------------------------------------+
|
|
void ShutdownPerformanceCollector()
|
|
{
|
|
if(g_performance_collector != NULL)
|
|
{
|
|
delete g_performance_collector;
|
|
g_performance_collector = NULL;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Convenience macros for timing |
|
|
//+------------------------------------------------------------------+
|
|
#define PERF_START(name) \
|
|
ulong __perf_start_##name = GetMicrosecondCount();
|
|
|
|
#define PERF_END(name) \
|
|
do { \
|
|
if(g_performance_collector != NULL) { \
|
|
g_performance_collector.RecordLatency(#name, GetMicrosecondCount() - __perf_start_##name); \
|
|
} \
|
|
} while(0)
|
|
|
|
#define PERF_END_CTX(name, context) \
|
|
do { \
|
|
if(g_performance_collector != NULL) { \
|
|
g_performance_collector.RecordLatency(#name, GetMicrosecondCount() - __perf_start_##name, context); \
|
|
} \
|
|
} while(0)
|
|
|
|
#endif // PERFORMANCE_METRICS_COLLECTOR_MQH
|