MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Refacto.../Telemetry/RefactoringTelemetry.cs

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);
}
}
}