588 lines
22 KiB
C#
Executable File
588 lines
22 KiB
C#
Executable File
using Microsoft.Extensions.Logging;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.Metrics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MarketAlly.AIPlugin.Refactoring.Telemetry
|
|
{
|
|
public interface IRefactoringTelemetry
|
|
{
|
|
Task<T> TrackOperationAsync<T>(
|
|
string operationName,
|
|
Func<Task<T>> operation,
|
|
Dictionary<string, object>? tags = null,
|
|
[CallerMemberName] string? callerName = null);
|
|
|
|
void RecordMetric(string metricName, double value, Dictionary<string, object>? tags = null);
|
|
void RecordCounter(string counterName, int value = 1, Dictionary<string, object>? tags = null);
|
|
void RecordDuration(string operationName, TimeSpan duration, Dictionary<string, object>? tags = null);
|
|
|
|
Activity? StartActivity(string activityName, Dictionary<string, object>? tags = null);
|
|
void SetActivityData(Activity? activity, string key, object value);
|
|
|
|
TelemetryStatistics GetStatistics();
|
|
void Flush();
|
|
}
|
|
|
|
public class RefactoringTelemetry : IRefactoringTelemetry, IDisposable
|
|
{
|
|
private readonly ILogger<RefactoringTelemetry>? _logger;
|
|
private readonly ActivitySource _activitySource;
|
|
private readonly Meter _meter;
|
|
|
|
// Metrics
|
|
private readonly Counter<long> _operationCounter;
|
|
private readonly Histogram<double> _operationDuration;
|
|
private readonly Counter<long> _errorCounter;
|
|
private readonly Histogram<double> _memoryUsage;
|
|
private readonly Histogram<long> _filesProcessed;
|
|
private readonly Histogram<double> _analysisComplexity;
|
|
|
|
// Statistics tracking
|
|
private readonly Dictionary<string, OperationStatistics> _operationStats = new();
|
|
private readonly object _statsLock = new();
|
|
|
|
public RefactoringTelemetry(ILogger<RefactoringTelemetry>? logger = null)
|
|
{
|
|
_logger = logger;
|
|
_activitySource = new ActivitySource("MarketAlly.AIPlugin.Refactoring");
|
|
_meter = new Meter("MarketAlly.AIPlugin.Refactoring");
|
|
|
|
// Initialize metrics
|
|
_operationCounter = _meter.CreateCounter<long>(
|
|
"refactoring.operations.total",
|
|
description: "Total number of refactoring operations");
|
|
|
|
_operationDuration = _meter.CreateHistogram<double>(
|
|
"refactoring.operation.duration",
|
|
unit: "ms",
|
|
description: "Duration of refactoring operations in milliseconds");
|
|
|
|
_errorCounter = _meter.CreateCounter<long>(
|
|
"refactoring.errors.total",
|
|
description: "Total number of errors during refactoring");
|
|
|
|
_memoryUsage = _meter.CreateHistogram<double>(
|
|
"refactoring.memory.usage",
|
|
unit: "bytes",
|
|
description: "Memory usage during refactoring operations");
|
|
|
|
_filesProcessed = _meter.CreateHistogram<long>(
|
|
"refactoring.files.processed",
|
|
description: "Number of files processed in operations");
|
|
|
|
_analysisComplexity = _meter.CreateHistogram<double>(
|
|
"refactoring.analysis.complexity",
|
|
description: "Complexity metrics from code analysis");
|
|
}
|
|
|
|
public async Task<T> TrackOperationAsync<T>(
|
|
string operationName,
|
|
Func<Task<T>> operation,
|
|
Dictionary<string, object>? tags = null,
|
|
[CallerMemberName] string? callerName = null)
|
|
{
|
|
using var activity = StartActivity($"{operationName}.{callerName}", tags);
|
|
var stopwatch = Stopwatch.StartNew();
|
|
var operationTags = CreateTags(operationName, tags);
|
|
|
|
try
|
|
{
|
|
// Record operation start
|
|
_operationCounter.Add(1, operationTags);
|
|
|
|
// Track memory before operation
|
|
var memoryBefore = GC.GetTotalMemory(false);
|
|
|
|
_logger?.LogDebug("Starting operation {OperationName} from {CallerName}", operationName, callerName);
|
|
|
|
// Execute operation
|
|
var result = await operation();
|
|
|
|
stopwatch.Stop();
|
|
|
|
// Track memory after operation
|
|
var memoryAfter = GC.GetTotalMemory(false);
|
|
var memoryDelta = memoryAfter - memoryBefore;
|
|
|
|
// Record successful completion
|
|
var successTags = CreateTags(operationName, tags, ("success", true));
|
|
_operationDuration.Record(stopwatch.Elapsed.TotalMilliseconds, successTags);
|
|
|
|
if (memoryDelta > 0)
|
|
{
|
|
_memoryUsage.Record(memoryDelta, successTags);
|
|
}
|
|
|
|
// Update statistics
|
|
UpdateOperationStatistics(operationName, stopwatch.Elapsed, true);
|
|
|
|
// Set activity data
|
|
activity?.SetTag("success", true);
|
|
activity?.SetTag("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
activity?.SetTag("memory_delta", memoryDelta);
|
|
|
|
_logger?.LogInformation("Operation {OperationName} completed successfully in {Duration}ms, Memory delta: {MemoryDelta} bytes",
|
|
operationName, stopwatch.ElapsedMilliseconds, memoryDelta);
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
stopwatch.Stop();
|
|
|
|
// Record error
|
|
var errorTags = CreateTags(operationName, tags, ("success", false), ("error_type", ex.GetType().Name));
|
|
_errorCounter.Add(1, errorTags);
|
|
_operationDuration.Record(stopwatch.Elapsed.TotalMilliseconds, errorTags);
|
|
|
|
// Update statistics
|
|
UpdateOperationStatistics(operationName, stopwatch.Elapsed, false);
|
|
|
|
// Set activity data
|
|
activity?.SetTag("success", false);
|
|
activity?.SetTag("error", ex.Message);
|
|
activity?.SetTag("error_type", ex.GetType().Name);
|
|
activity?.SetTag("duration_ms", stopwatch.ElapsedMilliseconds);
|
|
|
|
_logger?.LogError(ex, "Operation {OperationName} failed after {Duration}ms",
|
|
operationName, stopwatch.ElapsedMilliseconds);
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public void RecordMetric(string metricName, double value, Dictionary<string, object>? tags = null)
|
|
{
|
|
var metricTags = CreateTags(metricName, tags);
|
|
|
|
switch (metricName.ToLowerInvariant())
|
|
{
|
|
case "complexity":
|
|
case "cyclomatic_complexity":
|
|
case "cognitive_complexity":
|
|
_analysisComplexity.Record(value, metricTags);
|
|
break;
|
|
|
|
case "files_processed":
|
|
_filesProcessed.Record((long)value, metricTags);
|
|
break;
|
|
|
|
case "memory_usage":
|
|
_memoryUsage.Record(value, metricTags);
|
|
break;
|
|
|
|
default:
|
|
_logger?.LogDebug("Recording custom metric {MetricName}: {Value}", metricName, value);
|
|
// For custom metrics, we'd need a more flexible system
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void RecordCounter(string counterName, int value = 1, Dictionary<string, object>? tags = null)
|
|
{
|
|
var counterTags = CreateTags(counterName, tags);
|
|
|
|
switch (counterName.ToLowerInvariant())
|
|
{
|
|
case "operations":
|
|
_operationCounter.Add(value, counterTags);
|
|
break;
|
|
|
|
case "errors":
|
|
_errorCounter.Add(value, counterTags);
|
|
break;
|
|
|
|
default:
|
|
_logger?.LogDebug("Recording custom counter {CounterName}: {Value}", counterName, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void RecordDuration(string operationName, TimeSpan duration, Dictionary<string, object>? tags = null)
|
|
{
|
|
var durationTags = CreateTags(operationName, tags);
|
|
_operationDuration.Record(duration.TotalMilliseconds, durationTags);
|
|
|
|
UpdateOperationStatistics(operationName, duration, true);
|
|
}
|
|
|
|
public Activity? StartActivity(string activityName, Dictionary<string, object>? tags = null)
|
|
{
|
|
var activity = _activitySource.StartActivity(activityName);
|
|
|
|
if (activity != null && tags != null)
|
|
{
|
|
foreach (var tag in tags)
|
|
{
|
|
activity.SetTag(tag.Key, tag.Value?.ToString());
|
|
}
|
|
}
|
|
|
|
return activity;
|
|
}
|
|
|
|
public void SetActivityData(Activity? activity, string key, object value)
|
|
{
|
|
activity?.SetTag(key, value?.ToString());
|
|
}
|
|
|
|
public TelemetryStatistics GetStatistics()
|
|
{
|
|
lock (_statsLock)
|
|
{
|
|
var stats = new TelemetryStatistics
|
|
{
|
|
CollectedAt = DateTime.UtcNow,
|
|
TotalOperations = 0,
|
|
TotalErrors = 0,
|
|
AverageOperationDuration = TimeSpan.Zero,
|
|
OperationBreakdown = new Dictionary<string, OperationStatistics>()
|
|
};
|
|
|
|
foreach (var kvp in _operationStats)
|
|
{
|
|
stats.TotalOperations += kvp.Value.TotalCount;
|
|
stats.TotalErrors += kvp.Value.ErrorCount;
|
|
stats.OperationBreakdown[kvp.Key] = kvp.Value.Clone();
|
|
}
|
|
|
|
if (stats.TotalOperations > 0)
|
|
{
|
|
var totalDurationMs = _operationStats.Values.Sum(s => s.TotalDurationMs);
|
|
stats.AverageOperationDuration = TimeSpan.FromMilliseconds(totalDurationMs / stats.TotalOperations);
|
|
}
|
|
|
|
// Add system metrics
|
|
stats.CurrentMemoryUsage = GC.GetTotalMemory(false);
|
|
stats.Gen0Collections = GC.CollectionCount(0);
|
|
stats.Gen1Collections = GC.CollectionCount(1);
|
|
stats.Gen2Collections = GC.CollectionCount(2);
|
|
|
|
return stats;
|
|
}
|
|
}
|
|
|
|
public void Flush()
|
|
{
|
|
_logger?.LogInformation("Flushing telemetry data");
|
|
|
|
// In a real implementation, this would flush metrics to external systems
|
|
// For now, we'll just log current statistics
|
|
var stats = GetStatistics();
|
|
var statsJson = JsonSerializer.Serialize(stats, new JsonSerializerOptions { WriteIndented = true });
|
|
|
|
_logger?.LogInformation("Current telemetry statistics: {Statistics}", statsJson);
|
|
}
|
|
|
|
private KeyValuePair<string, object?>[] CreateTags(string operationName, Dictionary<string, object>? additionalTags, params (string Key, object Value)[] extraTags)
|
|
{
|
|
var tags = new List<KeyValuePair<string, object?>>
|
|
{
|
|
new("operation", operationName),
|
|
new("timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds())
|
|
};
|
|
|
|
if (additionalTags != null)
|
|
{
|
|
foreach (var tag in additionalTags)
|
|
{
|
|
tags.Add(new KeyValuePair<string, object?>(tag.Key, tag.Value));
|
|
}
|
|
}
|
|
|
|
foreach (var (key, value) in extraTags)
|
|
{
|
|
tags.Add(new KeyValuePair<string, object?>(key, value));
|
|
}
|
|
|
|
return tags.ToArray();
|
|
}
|
|
|
|
private void UpdateOperationStatistics(string operationName, TimeSpan duration, bool success)
|
|
{
|
|
lock (_statsLock)
|
|
{
|
|
if (!_operationStats.TryGetValue(operationName, out var stats))
|
|
{
|
|
stats = new OperationStatistics { OperationName = operationName };
|
|
_operationStats[operationName] = stats;
|
|
}
|
|
|
|
stats.TotalCount++;
|
|
stats.TotalDurationMs += duration.TotalMilliseconds;
|
|
|
|
if (success)
|
|
{
|
|
stats.SuccessCount++;
|
|
}
|
|
else
|
|
{
|
|
stats.ErrorCount++;
|
|
}
|
|
|
|
if (duration.TotalMilliseconds < stats.MinDurationMs || stats.MinDurationMs == 0)
|
|
{
|
|
stats.MinDurationMs = duration.TotalMilliseconds;
|
|
}
|
|
|
|
if (duration.TotalMilliseconds > stats.MaxDurationMs)
|
|
{
|
|
stats.MaxDurationMs = duration.TotalMilliseconds;
|
|
}
|
|
|
|
stats.AverageDurationMs = stats.TotalDurationMs / stats.TotalCount;
|
|
stats.LastUpdated = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_activitySource?.Dispose();
|
|
_meter?.Dispose();
|
|
}
|
|
}
|
|
|
|
public class TelemetryStatistics
|
|
{
|
|
public DateTime CollectedAt { get; set; }
|
|
public int TotalOperations { get; set; }
|
|
public int TotalErrors { get; set; }
|
|
public TimeSpan AverageOperationDuration { get; set; }
|
|
public Dictionary<string, OperationStatistics> OperationBreakdown { get; set; } = new();
|
|
|
|
// System metrics
|
|
public long CurrentMemoryUsage { get; set; }
|
|
public int Gen0Collections { get; set; }
|
|
public int Gen1Collections { get; set; }
|
|
public int Gen2Collections { get; set; }
|
|
|
|
public double SuccessRate => TotalOperations > 0 ? (double)(TotalOperations - TotalErrors) / TotalOperations : 0.0;
|
|
}
|
|
|
|
public class OperationStatistics
|
|
{
|
|
public string OperationName { get; set; } = string.Empty;
|
|
public int TotalCount { get; set; }
|
|
public int SuccessCount { get; set; }
|
|
public int ErrorCount { get; set; }
|
|
public double TotalDurationMs { get; set; }
|
|
public double AverageDurationMs { get; set; }
|
|
public double MinDurationMs { get; set; }
|
|
public double MaxDurationMs { get; set; }
|
|
public DateTime LastUpdated { get; set; }
|
|
|
|
public double SuccessRate => TotalCount > 0 ? (double)SuccessCount / TotalCount : 0.0;
|
|
|
|
public OperationStatistics Clone()
|
|
{
|
|
return new OperationStatistics
|
|
{
|
|
OperationName = OperationName,
|
|
TotalCount = TotalCount,
|
|
SuccessCount = SuccessCount,
|
|
ErrorCount = ErrorCount,
|
|
TotalDurationMs = TotalDurationMs,
|
|
AverageDurationMs = AverageDurationMs,
|
|
MinDurationMs = MinDurationMs,
|
|
MaxDurationMs = MaxDurationMs,
|
|
LastUpdated = LastUpdated
|
|
};
|
|
}
|
|
}
|
|
|
|
// Performance monitor for tracking system resources
|
|
public interface IPerformanceMonitor
|
|
{
|
|
SystemPerformanceMetrics GetCurrentMetrics();
|
|
void StartMonitoring();
|
|
void StopMonitoring();
|
|
Task<PerformanceReport> GenerateReportAsync(TimeSpan period);
|
|
}
|
|
|
|
public class SystemPerformanceMetrics
|
|
{
|
|
public DateTime Timestamp { get; set; }
|
|
public long MemoryUsageBytes { get; set; }
|
|
public double CpuUsagePercent { get; set; }
|
|
public int ThreadCount { get; set; }
|
|
public int HandleCount { get; set; }
|
|
public long PrivateMemoryBytes { get; set; }
|
|
public long WorkingSetBytes { get; set; }
|
|
public TimeSpan TotalProcessorTime { get; set; }
|
|
}
|
|
|
|
public class PerformanceReport
|
|
{
|
|
public DateTime StartTime { get; set; }
|
|
public DateTime EndTime { get; set; }
|
|
public TimeSpan Duration { get; set; }
|
|
public SystemPerformanceMetrics PeakMetrics { get; set; } = new();
|
|
public SystemPerformanceMetrics AverageMetrics { get; set; } = new();
|
|
public List<SystemPerformanceMetrics> Samples { get; set; } = new();
|
|
}
|
|
|
|
public class SystemPerformanceMonitor : IPerformanceMonitor, IDisposable
|
|
{
|
|
private readonly ILogger<SystemPerformanceMonitor>? _logger;
|
|
private readonly Timer _monitoringTimer;
|
|
private readonly List<SystemPerformanceMetrics> _metrics = new();
|
|
private readonly object _metricsLock = new();
|
|
private bool _isMonitoring = false;
|
|
|
|
public SystemPerformanceMonitor(ILogger<SystemPerformanceMonitor>? logger = null)
|
|
{
|
|
_logger = logger;
|
|
_monitoringTimer = new Timer(CollectMetrics, null, Timeout.Infinite, Timeout.Infinite);
|
|
}
|
|
|
|
public SystemPerformanceMetrics GetCurrentMetrics()
|
|
{
|
|
try
|
|
{
|
|
using var process = Process.GetCurrentProcess();
|
|
|
|
return new SystemPerformanceMetrics
|
|
{
|
|
Timestamp = DateTime.UtcNow,
|
|
MemoryUsageBytes = GC.GetTotalMemory(false),
|
|
PrivateMemoryBytes = process.PrivateMemorySize64,
|
|
WorkingSetBytes = process.WorkingSet64,
|
|
TotalProcessorTime = process.TotalProcessorTime,
|
|
ThreadCount = process.Threads.Count,
|
|
HandleCount = process.HandleCount,
|
|
CpuUsagePercent = 0 // Would need more sophisticated calculation
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning(ex, "Failed to collect performance metrics");
|
|
return new SystemPerformanceMetrics { Timestamp = DateTime.UtcNow };
|
|
}
|
|
}
|
|
|
|
public void StartMonitoring()
|
|
{
|
|
if (_isMonitoring) return;
|
|
|
|
_isMonitoring = true;
|
|
_monitoringTimer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(5)); // Collect every 5 seconds
|
|
_logger?.LogInformation("Performance monitoring started");
|
|
}
|
|
|
|
public void StopMonitoring()
|
|
{
|
|
if (!_isMonitoring) return;
|
|
|
|
_isMonitoring = false;
|
|
_monitoringTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
_logger?.LogInformation("Performance monitoring stopped");
|
|
}
|
|
|
|
public async Task<PerformanceReport> GenerateReportAsync(TimeSpan period)
|
|
{
|
|
var endTime = DateTime.UtcNow;
|
|
var startTime = endTime - period;
|
|
|
|
List<SystemPerformanceMetrics> relevantMetrics;
|
|
lock (_metricsLock)
|
|
{
|
|
relevantMetrics = _metrics
|
|
.Where(m => m.Timestamp >= startTime && m.Timestamp <= endTime)
|
|
.ToList();
|
|
}
|
|
|
|
var report = new PerformanceReport
|
|
{
|
|
StartTime = startTime,
|
|
EndTime = endTime,
|
|
Duration = period,
|
|
Samples = relevantMetrics
|
|
};
|
|
|
|
if (relevantMetrics.Any())
|
|
{
|
|
report.PeakMetrics = new SystemPerformanceMetrics
|
|
{
|
|
MemoryUsageBytes = relevantMetrics.Max(m => m.MemoryUsageBytes),
|
|
CpuUsagePercent = relevantMetrics.Max(m => m.CpuUsagePercent),
|
|
ThreadCount = relevantMetrics.Max(m => m.ThreadCount),
|
|
HandleCount = relevantMetrics.Max(m => m.HandleCount),
|
|
PrivateMemoryBytes = relevantMetrics.Max(m => m.PrivateMemoryBytes),
|
|
WorkingSetBytes = relevantMetrics.Max(m => m.WorkingSetBytes)
|
|
};
|
|
|
|
report.AverageMetrics = new SystemPerformanceMetrics
|
|
{
|
|
MemoryUsageBytes = (long)relevantMetrics.Average(m => m.MemoryUsageBytes),
|
|
CpuUsagePercent = relevantMetrics.Average(m => m.CpuUsagePercent),
|
|
ThreadCount = (int)relevantMetrics.Average(m => m.ThreadCount),
|
|
HandleCount = (int)relevantMetrics.Average(m => m.HandleCount),
|
|
PrivateMemoryBytes = (long)relevantMetrics.Average(m => m.PrivateMemoryBytes),
|
|
WorkingSetBytes = (long)relevantMetrics.Average(m => m.WorkingSetBytes)
|
|
};
|
|
}
|
|
|
|
return await Task.FromResult(report);
|
|
}
|
|
|
|
private void CollectMetrics(object? state)
|
|
{
|
|
if (!_isMonitoring) return;
|
|
|
|
try
|
|
{
|
|
var metrics = GetCurrentMetrics();
|
|
|
|
lock (_metricsLock)
|
|
{
|
|
_metrics.Add(metrics);
|
|
|
|
// Keep only last hour of metrics
|
|
var cutoff = DateTime.UtcNow.AddHours(-1);
|
|
var toRemove = _metrics.Where(m => m.Timestamp < cutoff).ToList();
|
|
foreach (var old in toRemove)
|
|
{
|
|
_metrics.Remove(old);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger?.LogWarning(ex, "Failed to collect performance metrics");
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_monitoringTimer?.Dispose();
|
|
}
|
|
}
|
|
|
|
// Factory for easy access
|
|
public static class TelemetryFactory
|
|
{
|
|
private static readonly Lazy<IRefactoringTelemetry> _defaultTelemetry =
|
|
new(() => new RefactoringTelemetry());
|
|
|
|
private static readonly Lazy<IPerformanceMonitor> _defaultPerformanceMonitor =
|
|
new(() => new SystemPerformanceMonitor());
|
|
|
|
public static IRefactoringTelemetry Default => _defaultTelemetry.Value;
|
|
public static IPerformanceMonitor PerformanceMonitor => _defaultPerformanceMonitor.Value;
|
|
|
|
public static IRefactoringTelemetry Create(ILogger<RefactoringTelemetry>? logger = null)
|
|
{
|
|
return new RefactoringTelemetry(logger);
|
|
}
|
|
|
|
public static IPerformanceMonitor CreatePerformanceMonitor(ILogger<SystemPerformanceMonitor>? logger = null)
|
|
{
|
|
return new SystemPerformanceMonitor(logger);
|
|
}
|
|
}
|
|
} |