383 lines
11 KiB
C#
Executable File
383 lines
11 KiB
C#
Executable File
using System.Diagnostics.Metrics;
|
|
using System.Diagnostics;
|
|
using Microsoft.Extensions.Logging;
|
|
using MarketAlly.AIPlugin.Context.Configuration;
|
|
|
|
namespace MarketAlly.AIPlugin.Context.Monitoring
|
|
{
|
|
/// <summary>
|
|
/// Provides comprehensive metrics and monitoring for context operations
|
|
/// </summary>
|
|
public class ContextMetrics : IDisposable
|
|
{
|
|
private readonly Meter _meter;
|
|
private readonly ILogger<ContextMetrics> _logger;
|
|
private readonly ContextConfiguration _configuration;
|
|
|
|
// Counters
|
|
private readonly Counter<long> _operationCounter;
|
|
private readonly Counter<long> _errorCounter;
|
|
private readonly Counter<long> _cacheHitCounter;
|
|
private readonly Counter<long> _cacheMissCounter;
|
|
|
|
// Histograms
|
|
private readonly Histogram<double> _operationDuration;
|
|
private readonly Histogram<double> _fileSize;
|
|
private readonly Histogram<long> _searchResultCount;
|
|
|
|
// Gauges (using UpDownCounters as approximation)
|
|
private readonly UpDownCounter<long> _activeConnections;
|
|
private readonly UpDownCounter<long> _contextEntriesCount;
|
|
private readonly UpDownCounter<long> _fileCacheSize;
|
|
|
|
// Activity sources for distributed tracing
|
|
private readonly ActivitySource _activitySource;
|
|
|
|
public ContextMetrics(ILogger<ContextMetrics> 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<long>(
|
|
"context_operations_total",
|
|
"Total number of context operations performed",
|
|
"operations");
|
|
|
|
_errorCounter = _meter.CreateCounter<long>(
|
|
"context_errors_total",
|
|
"Total number of context operation errors",
|
|
"errors");
|
|
|
|
_cacheHitCounter = _meter.CreateCounter<long>(
|
|
"context_cache_hits_total",
|
|
"Total number of cache hits",
|
|
"hits");
|
|
|
|
_cacheMissCounter = _meter.CreateCounter<long>(
|
|
"context_cache_misses_total",
|
|
"Total number of cache misses",
|
|
"misses");
|
|
|
|
// Initialize histograms
|
|
_operationDuration = _meter.CreateHistogram<double>(
|
|
"context_operation_duration_ms",
|
|
"Duration of context operations in milliseconds",
|
|
"ms");
|
|
|
|
_fileSize = _meter.CreateHistogram<double>(
|
|
"context_file_size_bytes",
|
|
"Size of context files in bytes",
|
|
"bytes");
|
|
|
|
_searchResultCount = _meter.CreateHistogram<long>(
|
|
"context_search_results_count",
|
|
"Number of results returned by search operations",
|
|
"results");
|
|
|
|
// Initialize gauges (using UpDownCounters)
|
|
_activeConnections = _meter.CreateUpDownCounter<long>(
|
|
"context_active_connections",
|
|
"Number of active context connections",
|
|
"connections");
|
|
|
|
_contextEntriesCount = _meter.CreateUpDownCounter<long>(
|
|
"context_entries_total",
|
|
"Total number of context entries stored",
|
|
"entries");
|
|
|
|
_fileCacheSize = _meter.CreateUpDownCounter<long>(
|
|
"context_file_cache_size_bytes",
|
|
"Current size of file cache in bytes",
|
|
"bytes");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records the start of an operation and returns a disposable tracker
|
|
/// </summary>
|
|
public OperationTracker StartOperation(string operationType, Dictionary<string, object>? 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());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records a completed operation
|
|
/// </summary>
|
|
internal void RecordOperation(string operationType, TimeSpan duration, bool success, Dictionary<string, object>? 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");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records cache hit metrics
|
|
/// </summary>
|
|
public void RecordCacheHit(string cacheType, Dictionary<string, object>? tags = null)
|
|
{
|
|
var cacheTags = CreateTags("cache", "hit", tags);
|
|
cacheTags = cacheTags.Append(new KeyValuePair<string, object?>("cache_type", cacheType)).ToArray();
|
|
|
|
_cacheHitCounter.Add(1, cacheTags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records cache miss metrics
|
|
/// </summary>
|
|
public void RecordCacheMiss(string cacheType, Dictionary<string, object>? tags = null)
|
|
{
|
|
var cacheTags = CreateTags("cache", "miss", tags);
|
|
cacheTags = cacheTags.Append(new KeyValuePair<string, object?>("cache_type", cacheType)).ToArray();
|
|
|
|
_cacheMissCounter.Add(1, cacheTags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records file operation metrics
|
|
/// </summary>
|
|
public void RecordFileOperation(string operation, long fileSizeBytes, TimeSpan duration, bool success)
|
|
{
|
|
var tags = CreateTags("file", success ? "success" : "error", null);
|
|
tags = tags.Append(new KeyValuePair<string, object?>("file_operation", operation)).ToArray();
|
|
|
|
_fileSize.Record(fileSizeBytes, tags);
|
|
_operationDuration.Record(duration.TotalMilliseconds, tags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records search operation metrics
|
|
/// </summary>
|
|
public void RecordSearchOperation(string searchType, int resultCount, TimeSpan duration, bool usedCache = false)
|
|
{
|
|
var tags = CreateTags("search", "success", null);
|
|
tags = tags.Append(new KeyValuePair<string, object?>("search_type", searchType)).ToArray();
|
|
tags = tags.Append(new KeyValuePair<string, object?>("used_cache", usedCache)).ToArray();
|
|
|
|
_searchResultCount.Record(resultCount, tags);
|
|
_operationDuration.Record(duration.TotalMilliseconds, tags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the total number of context entries
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the file cache size
|
|
/// </summary>
|
|
public void UpdateFileCacheSize(long newSizeBytes)
|
|
{
|
|
var tags = CreateTags("cache", "update", null);
|
|
_fileCacheSize.Add(newSizeBytes, tags);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records an error with detailed information
|
|
/// </summary>
|
|
public void RecordError(string operationType, Exception exception, Dictionary<string, object>? tags = null)
|
|
{
|
|
var errorTags = CreateTags(operationType, "error", tags);
|
|
errorTags = errorTags.Append(new KeyValuePair<string, object?>("error_type", exception.GetType().Name)).ToArray();
|
|
|
|
_errorCounter.Add(1, errorTags);
|
|
|
|
_logger.LogError(exception,
|
|
"Error in {OperationType}: {ErrorMessage}",
|
|
operationType, exception.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets current performance metrics
|
|
/// </summary>
|
|
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
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates standardized tags for metrics
|
|
/// </summary>
|
|
private KeyValuePair<string, object?>[] CreateTags(string operationType, string status, Dictionary<string, object>? additionalTags)
|
|
{
|
|
var tags = new List<KeyValuePair<string, object?>>
|
|
{
|
|
new("operation_type", operationType),
|
|
new("status", status),
|
|
new("version", "1.0.0")
|
|
};
|
|
|
|
if (additionalTags != null)
|
|
{
|
|
foreach (var tag in additionalTags)
|
|
{
|
|
tags.Add(new KeyValuePair<string, object?>(tag.Key, tag.Value));
|
|
}
|
|
}
|
|
|
|
return tags.ToArray();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_meter?.Dispose();
|
|
_activitySource?.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tracks the duration and outcome of an operation
|
|
/// </summary>
|
|
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<string, object> _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<string, object>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a tag to the operation
|
|
/// </summary>
|
|
public OperationTracker AddTag(string key, object value)
|
|
{
|
|
_tags[key] = value;
|
|
_activity?.SetTag(key, value);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records the operation as successful
|
|
/// </summary>
|
|
public void RecordSuccess()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_stopwatch.Stop();
|
|
_metrics.RecordOperation(_operationType, _stopwatch.Elapsed, true, _tags);
|
|
_activity?.SetStatus(ActivityStatusCode.Ok);
|
|
_disposed = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records the operation as failed
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current performance metrics snapshot
|
|
/// </summary>
|
|
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; }
|
|
}
|
|
} |