224 lines
7.8 KiB
C#
224 lines
7.8 KiB
C#
using EonaCat.DoxaApi.Models;
|
|
|
|
namespace EonaCat.DoxaApi.Generation
|
|
{
|
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
// See the LICENSE file or go to https://EonaCat.com/license for full license details.
|
|
|
|
/// <summary>
|
|
/// Collects and manages API metrics and analytics.
|
|
/// </summary>
|
|
public class MetricsCollector
|
|
{
|
|
private readonly Dictionary<string, ApiMetrics> _metrics = new();
|
|
private readonly List<RequestMetadata> _requestHistory = new();
|
|
private readonly object _lock = new();
|
|
private readonly DateTime _startTime = DateTime.UtcNow;
|
|
|
|
/// <summary>
|
|
/// Records a request to an endpoint.
|
|
/// </summary>
|
|
public void RecordRequest(string method, string path, int statusCode, long responseTimeMs, long responseSize, string? userAgent = null, string? ipAddress = null)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var key = $"{method}:{path}";
|
|
|
|
if (!_metrics.ContainsKey(key))
|
|
{
|
|
_metrics[key] = new ApiMetrics
|
|
{
|
|
EndpointId = key,
|
|
Method = method,
|
|
Path = path,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
var metric = _metrics[key];
|
|
metric.TotalRequests++;
|
|
metric.LastRequestAt = DateTime.UtcNow;
|
|
|
|
if (statusCode >= 200 && statusCode < 300)
|
|
{
|
|
metric.SuccessfulRequests++;
|
|
}
|
|
else if (statusCode >= 400)
|
|
{
|
|
metric.FailedRequests++;
|
|
}
|
|
|
|
// Track status codes
|
|
if (!metric.StatusCodes.ContainsKey(statusCode))
|
|
{
|
|
metric.StatusCodes[statusCode] = 0;
|
|
}
|
|
metric.StatusCodes[statusCode]++;
|
|
|
|
// Calculate running average response time
|
|
metric.AverageResponseTimeMs = (metric.AverageResponseTimeMs * (metric.TotalRequests - 1) + responseTimeMs) / metric.TotalRequests;
|
|
|
|
// Calculate running average response size
|
|
metric.AverageResponseSize = (metric.AverageResponseSize * (metric.TotalRequests - 1) + responseSize) / metric.TotalRequests;
|
|
|
|
// Add to request history (keep last 1000)
|
|
_requestHistory.Add(new RequestMetadata
|
|
{
|
|
Method = method,
|
|
Path = path,
|
|
StatusCode = statusCode,
|
|
ResponseTimeMs = responseTimeMs,
|
|
ResponseSize = responseSize,
|
|
Timestamp = DateTime.UtcNow,
|
|
UserAgent = userAgent,
|
|
IpAddress = ipAddress
|
|
});
|
|
|
|
if (_requestHistory.Count > 1000)
|
|
{
|
|
_requestHistory.RemoveAt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records an error for an endpoint.
|
|
/// </summary>
|
|
public void RecordError(string method, string path, string errorMessage, string? userAgent = null, string? ipAddress = null)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var key = $"{method}:{path}";
|
|
|
|
if (!_metrics.ContainsKey(key))
|
|
{
|
|
_metrics[key] = new ApiMetrics
|
|
{
|
|
EndpointId = key,
|
|
Method = method,
|
|
Path = path,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
_metrics[key].FailedRequests++;
|
|
|
|
_requestHistory.Add(new RequestMetadata
|
|
{
|
|
Method = method,
|
|
Path = path,
|
|
StatusCode = 500,
|
|
ResponseTimeMs = 0,
|
|
Timestamp = DateTime.UtcNow,
|
|
UserAgent = userAgent,
|
|
IpAddress = ipAddress,
|
|
Error = errorMessage
|
|
});
|
|
|
|
if (_requestHistory.Count > 1000)
|
|
{
|
|
_requestHistory.RemoveAt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets metrics for a specific endpoint.
|
|
/// </summary>
|
|
public ApiMetrics? GetMetrics(string method, string path)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var key = $"{method}:{path}";
|
|
return _metrics.ContainsKey(key) ? _metrics[key] : null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all collected metrics.
|
|
/// </summary>
|
|
public IEnumerable<ApiMetrics> GetAllMetrics()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return _metrics.Values.ToList();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the analytics dashboard data.
|
|
/// </summary>
|
|
public AnalyticsDashboard GetDashboard()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
var allMetrics = _metrics.Values.ToList();
|
|
var totalRequests = allMetrics.Sum(m => m.TotalRequests);
|
|
var totalErrors = allMetrics.Sum(m => m.FailedRequests);
|
|
|
|
var topEndpoints = allMetrics
|
|
.OrderByDescending(m => m.TotalRequests)
|
|
.Take(10)
|
|
.Select(m => new EndpointUsageSummary
|
|
{
|
|
Method = m.Method,
|
|
Path = m.Path,
|
|
RequestCount = m.TotalRequests,
|
|
ErrorCount = m.FailedRequests,
|
|
AverageResponseTimeMs = m.AverageResponseTimeMs,
|
|
LastCalled = m.LastRequestAt
|
|
})
|
|
.ToList();
|
|
|
|
var recentRequests = _requestHistory
|
|
.OrderByDescending(r => r.Timestamp)
|
|
.Take(20)
|
|
.ToList();
|
|
|
|
var uptime = DateTime.UtcNow - _startTime;
|
|
var requestsPerSecond = totalRequests > 0 ? totalRequests / uptime.TotalSeconds : 0;
|
|
|
|
return new AnalyticsDashboard
|
|
{
|
|
TotalEndpoints = _metrics.Count,
|
|
TotalRequests = totalRequests,
|
|
TotalErrors = totalErrors,
|
|
AverageResponseTime = allMetrics.Any() ? allMetrics.Average(m => m.AverageResponseTimeMs) : 0,
|
|
PeakRequestsPerSecond = requestsPerSecond,
|
|
Uptime = uptime,
|
|
TopEndpoints = topEndpoints,
|
|
RecentRequests = recentRequests,
|
|
ErrorRate = totalRequests > 0 ? (double)totalErrors / totalRequests : 0,
|
|
GeneratedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears all collected metrics and history.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_metrics.Clear();
|
|
_requestHistory.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets recent requests (last N).
|
|
/// </summary>
|
|
public IEnumerable<RequestMetadata> GetRecentRequests(int count = 50)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
return _requestHistory
|
|
.OrderByDescending(r => r.Timestamp)
|
|
.Take(count)
|
|
.ToList();
|
|
}
|
|
}
|
|
}
|
|
}
|