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