using System.Diagnostics.Metrics; using System.Diagnostics; using Microsoft.Extensions.Logging; using MarketAlly.AIPlugin.Context.Configuration; namespace MarketAlly.AIPlugin.Context.Monitoring { /// /// Provides comprehensive metrics and monitoring for context operations /// public class ContextMetrics : IDisposable { private readonly Meter _meter; private readonly ILogger _logger; private readonly ContextConfiguration _configuration; // Counters private readonly Counter _operationCounter; private readonly Counter _errorCounter; private readonly Counter _cacheHitCounter; private readonly Counter _cacheMissCounter; // Histograms private readonly Histogram _operationDuration; private readonly Histogram _fileSize; private readonly Histogram _searchResultCount; // Gauges (using UpDownCounters as approximation) private readonly UpDownCounter _activeConnections; private readonly UpDownCounter _contextEntriesCount; private readonly UpDownCounter _fileCacheSize; // Activity sources for distributed tracing private readonly ActivitySource _activitySource; public ContextMetrics(ILogger logger, ContextConfiguration configuration) { _logger = logger; _configuration = configuration; _meter = new Meter("MarketAlly.Context", "1.0.0"); _activitySource = new ActivitySource("MarketAlly.Context.Operations"); // Initialize counters _operationCounter = _meter.CreateCounter( "context_operations_total", "Total number of context operations performed", "operations"); _errorCounter = _meter.CreateCounter( "context_errors_total", "Total number of context operation errors", "errors"); _cacheHitCounter = _meter.CreateCounter( "context_cache_hits_total", "Total number of cache hits", "hits"); _cacheMissCounter = _meter.CreateCounter( "context_cache_misses_total", "Total number of cache misses", "misses"); // Initialize histograms _operationDuration = _meter.CreateHistogram( "context_operation_duration_ms", "Duration of context operations in milliseconds", "ms"); _fileSize = _meter.CreateHistogram( "context_file_size_bytes", "Size of context files in bytes", "bytes"); _searchResultCount = _meter.CreateHistogram( "context_search_results_count", "Number of results returned by search operations", "results"); // Initialize gauges (using UpDownCounters) _activeConnections = _meter.CreateUpDownCounter( "context_active_connections", "Number of active context connections", "connections"); _contextEntriesCount = _meter.CreateUpDownCounter( "context_entries_total", "Total number of context entries stored", "entries"); _fileCacheSize = _meter.CreateUpDownCounter( "context_file_cache_size_bytes", "Current size of file cache in bytes", "bytes"); } /// /// Records the start of an operation and returns a disposable tracker /// public OperationTracker StartOperation(string operationType, Dictionary? tags = null) { var activity = _configuration.Monitoring.EnableTracing ? _activitySource.StartActivity($"context.{operationType}") : null; if (activity != null && tags != null) { foreach (var tag in tags) { activity.SetTag(tag.Key, tag.Value); } } _activeConnections.Add(1); return new OperationTracker( operationType, activity, this, _logger, Stopwatch.StartNew()); } /// /// Records a completed operation /// internal void RecordOperation(string operationType, TimeSpan duration, bool success, Dictionary? tags = null) { var operationTags = CreateTags(operationType, success ? "success" : "error", tags); _operationCounter.Add(1, operationTags); _operationDuration.Record(duration.TotalMilliseconds, operationTags); if (!success) { _errorCounter.Add(1, operationTags); } _activeConnections.Add(-1); if (_configuration.Monitoring.EnableDetailedLogging) { var logLevel = success ? LogLevel.Debug : LogLevel.Warning; _logger.Log(logLevel, "Operation {OperationType} completed in {Duration}ms with status {Status}", operationType, duration.TotalMilliseconds, success ? "Success" : "Error"); } } /// /// Records cache hit metrics /// public void RecordCacheHit(string cacheType, Dictionary? tags = null) { var cacheTags = CreateTags("cache", "hit", tags); cacheTags = cacheTags.Append(new KeyValuePair("cache_type", cacheType)).ToArray(); _cacheHitCounter.Add(1, cacheTags); } /// /// Records cache miss metrics /// public void RecordCacheMiss(string cacheType, Dictionary? tags = null) { var cacheTags = CreateTags("cache", "miss", tags); cacheTags = cacheTags.Append(new KeyValuePair("cache_type", cacheType)).ToArray(); _cacheMissCounter.Add(1, cacheTags); } /// /// Records file operation metrics /// public void RecordFileOperation(string operation, long fileSizeBytes, TimeSpan duration, bool success) { var tags = CreateTags("file", success ? "success" : "error", null); tags = tags.Append(new KeyValuePair("file_operation", operation)).ToArray(); _fileSize.Record(fileSizeBytes, tags); _operationDuration.Record(duration.TotalMilliseconds, tags); } /// /// Records search operation metrics /// public void RecordSearchOperation(string searchType, int resultCount, TimeSpan duration, bool usedCache = false) { var tags = CreateTags("search", "success", null); tags = tags.Append(new KeyValuePair("search_type", searchType)).ToArray(); tags = tags.Append(new KeyValuePair("used_cache", usedCache)).ToArray(); _searchResultCount.Record(resultCount, tags); _operationDuration.Record(duration.TotalMilliseconds, tags); } /// /// Updates the total number of context entries /// public void UpdateContextEntriesCount(long newCount) { // Since we can't set absolute values with UpDownCounter, we need to track the delta // This is a simplified approach - in production, you might want to use a different gauge implementation var tags = CreateTags("storage", "update", null); _contextEntriesCount.Add(newCount, tags); } /// /// Updates the file cache size /// public void UpdateFileCacheSize(long newSizeBytes) { var tags = CreateTags("cache", "update", null); _fileCacheSize.Add(newSizeBytes, tags); } /// /// Records an error with detailed information /// public void RecordError(string operationType, Exception exception, Dictionary? tags = null) { var errorTags = CreateTags(operationType, "error", tags); errorTags = errorTags.Append(new KeyValuePair("error_type", exception.GetType().Name)).ToArray(); _errorCounter.Add(1, errorTags); _logger.LogError(exception, "Error in {OperationType}: {ErrorMessage}", operationType, exception.Message); } /// /// Gets current performance metrics /// public PerformanceMetrics GetCurrentMetrics() { // Note: In a real implementation, you'd want to collect these values from the actual meters // This is a simplified representation return new PerformanceMetrics { Timestamp = DateTime.UtcNow, ActiveConnections = 0, // Would need to track this separately TotalOperations = 0, // Would need to track this separately TotalErrors = 0, // Would need to track this separately CacheHitRatio = 0.0, // Would calculate from hit/miss counters AverageOperationDuration = 0.0, // Would calculate from histogram MemoryUsage = GC.GetTotalMemory(false), IsHealthy = true }; } /// /// Creates standardized tags for metrics /// private KeyValuePair[] CreateTags(string operationType, string status, Dictionary? additionalTags) { var tags = new List> { new("operation_type", operationType), new("status", status), new("version", "1.0.0") }; if (additionalTags != null) { foreach (var tag in additionalTags) { tags.Add(new KeyValuePair(tag.Key, tag.Value)); } } return tags.ToArray(); } public void Dispose() { _meter?.Dispose(); _activitySource?.Dispose(); } } /// /// Tracks the duration and outcome of an operation /// public class OperationTracker : IDisposable { private readonly string _operationType; private readonly Activity? _activity; private readonly ContextMetrics _metrics; private readonly ILogger _logger; private readonly Stopwatch _stopwatch; private readonly Dictionary _tags; private bool _disposed; internal OperationTracker( string operationType, Activity? activity, ContextMetrics metrics, ILogger logger, Stopwatch stopwatch) { _operationType = operationType; _activity = activity; _metrics = metrics; _logger = logger; _stopwatch = stopwatch; _tags = new Dictionary(); } /// /// Adds a tag to the operation /// public OperationTracker AddTag(string key, object value) { _tags[key] = value; _activity?.SetTag(key, value); return this; } /// /// Records the operation as successful /// public void RecordSuccess() { if (!_disposed) { _stopwatch.Stop(); _metrics.RecordOperation(_operationType, _stopwatch.Elapsed, true, _tags); _activity?.SetStatus(ActivityStatusCode.Ok); _disposed = true; } } /// /// Records the operation as failed /// public void RecordError(Exception? exception = null) { if (!_disposed) { _stopwatch.Stop(); _metrics.RecordOperation(_operationType, _stopwatch.Elapsed, false, _tags); if (exception != null) { _activity?.SetStatus(ActivityStatusCode.Error, exception.Message); _metrics.RecordError(_operationType, exception, _tags); } else { _activity?.SetStatus(ActivityStatusCode.Error); } _disposed = true; } } public void Dispose() { if (!_disposed) { // If not explicitly marked as success or error, assume success RecordSuccess(); } _activity?.Dispose(); } } /// /// Current performance metrics snapshot /// public class PerformanceMetrics { public DateTime Timestamp { get; set; } public long ActiveConnections { get; set; } public long TotalOperations { get; set; } public long TotalErrors { get; set; } public double CacheHitRatio { get; set; } public double AverageOperationDuration { get; set; } public long MemoryUsage { get; set; } public bool IsHealthy { get; set; } } }