Simplify MetricsService implementation to remove database dependencies
This commit is contained in:
parent
71fc571f38
commit
681e1aa3e9
@ -1,528 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TransmissionRssManager.Core;
|
||||
using TransmissionRssManager.Data.Repositories;
|
||||
|
||||
namespace TransmissionRssManager.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for the metrics service that provides dashboard statistics and performance data
|
||||
/// </summary>
|
||||
public interface IMetricsService
|
||||
{
|
||||
Task<DashboardStats> GetDashboardStatsAsync();
|
||||
Task<List<HistoricalDataPoint>> GetDownloadHistoryAsync(int days = 30);
|
||||
Task<List<CategoryStats>> GetCategoryStatsAsync();
|
||||
Task<SystemStatus> GetSystemStatusAsync();
|
||||
Task<Dictionary<string, object>> GetDashboardStatsAsync();
|
||||
Task<Dictionary<string, long>> EstimateDiskUsageAsync();
|
||||
Task<Dictionary<string, double>> GetPerformanceMetricsAsync();
|
||||
Task<Dictionary<string, object>> GetSystemStatusAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service that provides metrics and statistics about downloads, system status, and performance
|
||||
/// </summary>
|
||||
public class MetricsService : IMetricsService
|
||||
{
|
||||
private readonly ILogger<MetricsService> _logger;
|
||||
private readonly ITransmissionClient _transmissionClient;
|
||||
private readonly IRepository<TransmissionRssManager.Data.Models.RssFeed> _feedRepository;
|
||||
private readonly IRepository<TransmissionRssManager.Data.Models.RssFeedItem> _feedItemRepository;
|
||||
private readonly IRepository<TransmissionRssManager.Data.Models.Torrent> _torrentRepository;
|
||||
private readonly IRepository<TransmissionRssManager.Data.Models.SystemLogEntry> _logRepository;
|
||||
private readonly ILoggingService _loggingService;
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
// Helper method to map database Torrent model to Core.TorrentInfo
|
||||
private Core.TorrentInfo MapToTorrentInfo(TransmissionRssManager.Data.Models.Torrent torrent)
|
||||
{
|
||||
return new Core.TorrentInfo
|
||||
{
|
||||
Id = torrent.Id,
|
||||
Name = torrent.Name,
|
||||
Status = torrent.Status,
|
||||
PercentDone = torrent.PercentDone,
|
||||
TotalSize = torrent.TotalSize,
|
||||
DownloadDir = torrent.DownloadDirectory ?? "",
|
||||
AddedDate = torrent.AddedOn,
|
||||
CompletedDate = torrent.CompletedOn,
|
||||
DownloadedEver = torrent.DownloadedEver,
|
||||
UploadedEver = torrent.UploadedEver,
|
||||
UploadRatio = (int)torrent.UploadRatio,
|
||||
ErrorString = torrent.ErrorMessage ?? "",
|
||||
HashString = torrent.Hash,
|
||||
PeersConnected = torrent.PeersConnected,
|
||||
DownloadSpeed = torrent.DownloadSpeed,
|
||||
UploadSpeed = torrent.UploadSpeed,
|
||||
Category = torrent.Category ?? "",
|
||||
HasMetadata = torrent.HasMetadata,
|
||||
TransmissionInstance = torrent.TransmissionInstance ?? "default",
|
||||
SourceFeedId = torrent.RssFeedItemId?.ToString() ?? "",
|
||||
IsPostProcessed = torrent.PostProcessed
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method to map database RssFeed model to Core.RssFeed
|
||||
private Core.RssFeed MapToRssFeed(TransmissionRssManager.Data.Models.RssFeed feed)
|
||||
{
|
||||
var result = new Core.RssFeed
|
||||
{
|
||||
Id = feed.Id.ToString(),
|
||||
Name = feed.Name,
|
||||
Url = feed.Url,
|
||||
AutoDownload = feed.Enabled,
|
||||
LastChecked = feed.LastCheckedAt,
|
||||
TransmissionInstanceId = feed.TransmissionInstanceId ?? "default",
|
||||
Schedule = feed.Schedule,
|
||||
Enabled = feed.Enabled,
|
||||
MaxHistoryItems = feed.MaxHistoryItems,
|
||||
DefaultCategory = feed.DefaultCategory ?? "",
|
||||
ErrorCount = feed.ErrorCount,
|
||||
LastError = feed.LastError != null ? feed.LastCheckedAt : null,
|
||||
LastErrorMessage = feed.LastError
|
||||
};
|
||||
|
||||
// Add rules to the feed
|
||||
if (feed.Rules != null)
|
||||
{
|
||||
foreach (var rule in feed.Rules)
|
||||
{
|
||||
result.AdvancedRules.Add(new Core.RssFeedRule
|
||||
{
|
||||
Id = rule.Id.ToString(),
|
||||
Name = rule.Name,
|
||||
Pattern = rule.IncludePattern ?? "",
|
||||
IsRegex = rule.UseRegex,
|
||||
IsEnabled = rule.Enabled,
|
||||
IsCaseSensitive = false, // Default value as this field doesn't exist in the DB model
|
||||
Category = rule.CustomSavePath ?? "",
|
||||
Priority = rule.Priority,
|
||||
Action = rule.EnablePostProcessing ? "process" : "download",
|
||||
DestinationFolder = rule.CustomSavePath ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MetricsService(
|
||||
ILogger<MetricsService> logger,
|
||||
ITransmissionClient transmissionClient,
|
||||
IRepository<TransmissionRssManager.Data.Models.RssFeed> feedRepository,
|
||||
IRepository<TransmissionRssManager.Data.Models.RssFeedItem> feedItemRepository,
|
||||
IRepository<TransmissionRssManager.Data.Models.Torrent> torrentRepository,
|
||||
IRepository<TransmissionRssManager.Data.Models.SystemLogEntry> logRepository,
|
||||
ILoggingService loggingService,
|
||||
IConfigService configService)
|
||||
{
|
||||
_logger = logger;
|
||||
_transmissionClient = transmissionClient;
|
||||
_feedRepository = feedRepository;
|
||||
_feedItemRepository = feedItemRepository;
|
||||
_torrentRepository = torrentRepository;
|
||||
_logRepository = logRepository;
|
||||
_loggingService = loggingService;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
public async Task<DashboardStats> GetDashboardStatsAsync()
|
||||
/// <summary>
|
||||
/// Gets dashboard statistics including active downloads, upload/download speeds, etc.
|
||||
/// </summary>
|
||||
public async Task<Dictionary<string, object>> GetDashboardStatsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var today = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
var stats = new Dictionary<string, object>();
|
||||
var torrents = await _transmissionClient.GetTorrentsAsync();
|
||||
|
||||
// Combine data from both Transmission and database
|
||||
// Get active torrents from Transmission for real-time data
|
||||
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync();
|
||||
// Calculate basic stats
|
||||
stats["TotalTorrents"] = torrents.Count;
|
||||
stats["ActiveDownloads"] = torrents.Count(t => t.Status == "Downloading");
|
||||
stats["SeedingTorrents"] = torrents.Count(t => t.Status == "Seeding");
|
||||
stats["CompletedTorrents"] = torrents.Count(t => t.IsFinished);
|
||||
stats["TotalDownloaded"] = torrents.Sum(t => t.DownloadedEver);
|
||||
stats["TotalUploaded"] = torrents.Sum(t => t.UploadedEver);
|
||||
stats["DownloadSpeed"] = torrents.Sum(t => t.DownloadSpeed);
|
||||
stats["UploadSpeed"] = torrents.Sum(t => t.UploadSpeed);
|
||||
|
||||
// Get database torrent data for historical information
|
||||
var dbTorrents = await _torrentRepository.Query().ToListAsync();
|
||||
// Calculate total size
|
||||
long totalSize = torrents.Sum(t => t.TotalSize);
|
||||
stats["TotalSize"] = totalSize;
|
||||
|
||||
// Count active downloads (status is downloading)
|
||||
var activeDownloads = transmissionTorrents.Count(t => t.Status == "Downloading");
|
||||
|
||||
// Count seeding torrents (status is seeding)
|
||||
var seedingTorrents = transmissionTorrents.Count(t => t.Status == "Seeding");
|
||||
|
||||
// Get active feeds count
|
||||
var activeFeeds = await _feedRepository.Query()
|
||||
.Where(f => f.Enabled)
|
||||
.CountAsync();
|
||||
|
||||
// Get completed downloads today
|
||||
var completedToday = dbTorrents
|
||||
.Count(t => t.CompletedOn.HasValue && t.CompletedOn.Value >= today);
|
||||
|
||||
// Get added today
|
||||
var addedToday = dbTorrents
|
||||
.Count(t => t.AddedOn >= today);
|
||||
|
||||
// Get matched count (all time)
|
||||
var matchedCount = await _feedItemRepository.Query()
|
||||
.Where(i => i.MatchedRuleId != null)
|
||||
.CountAsync();
|
||||
|
||||
// Calculate download/upload speeds from Transmission (real-time data)
|
||||
double downloadSpeed = transmissionTorrents.Sum(t => t.DownloadSpeed);
|
||||
double uploadSpeed = transmissionTorrents.Sum(t => t.UploadSpeed);
|
||||
|
||||
// Update database objects with transmission data
|
||||
// This helps keep database in sync with transmission for metrics
|
||||
foreach (var transmissionTorrent in transmissionTorrents)
|
||||
{
|
||||
var dbTorrent = dbTorrents.FirstOrDefault(t => t.TransmissionId == transmissionTorrent.Id);
|
||||
if (dbTorrent != null)
|
||||
{
|
||||
// Update database with current speeds and status for the background service to store
|
||||
dbTorrent.DownloadSpeed = transmissionTorrent.DownloadSpeed;
|
||||
dbTorrent.UploadSpeed = transmissionTorrent.UploadSpeed;
|
||||
dbTorrent.Status = transmissionTorrent.Status;
|
||||
dbTorrent.PercentDone = transmissionTorrent.PercentDone;
|
||||
dbTorrent.DownloadedEver = transmissionTorrent.DownloadedEver;
|
||||
dbTorrent.UploadedEver = transmissionTorrent.UploadedEver;
|
||||
dbTorrent.PeersConnected = transmissionTorrent.PeersConnected;
|
||||
|
||||
// Update the database object
|
||||
await _torrentRepository.UpdateAsync(dbTorrent);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the changes
|
||||
await _torrentRepository.SaveChangesAsync();
|
||||
|
||||
// Calculate total downloaded and uploaded
|
||||
// Use Transmission for active torrents (more accurate) and database for historical torrents
|
||||
var totalDownloaded = transmissionTorrents.Sum(t => t.DownloadedEver);
|
||||
var totalUploaded = transmissionTorrents.Sum(t => t.UploadedEver);
|
||||
|
||||
// Add historical data from database for torrents that are no longer in Transmission
|
||||
var transmissionIds = transmissionTorrents.Select(t => t.Id).ToHashSet();
|
||||
var historicalTorrents = dbTorrents.Where(t => t.TransmissionId.HasValue && !transmissionIds.Contains(t.TransmissionId.Value));
|
||||
totalDownloaded += historicalTorrents.Sum(t => t.DownloadedEver);
|
||||
totalUploaded += historicalTorrents.Sum(t => t.UploadedEver);
|
||||
|
||||
return new DashboardStats
|
||||
{
|
||||
ActiveDownloads = activeDownloads,
|
||||
SeedingTorrents = seedingTorrents,
|
||||
ActiveFeeds = activeFeeds,
|
||||
CompletedToday = completedToday,
|
||||
AddedToday = addedToday,
|
||||
FeedsCount = await _feedRepository.Query().CountAsync(),
|
||||
MatchedCount = matchedCount,
|
||||
DownloadSpeed = downloadSpeed,
|
||||
UploadSpeed = uploadSpeed,
|
||||
TotalDownloaded = totalDownloaded,
|
||||
TotalUploaded = totalUploaded
|
||||
};
|
||||
return stats;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting dashboard stats");
|
||||
_loggingService.Log(
|
||||
LogLevel.Error,
|
||||
$"Error getting dashboard stats: {ex.Message}",
|
||||
"MetricsService",
|
||||
new Dictionary<string, string> {
|
||||
{ "ErrorMessage", ex.Message },
|
||||
{ "StackTrace", ex.StackTrace }
|
||||
}
|
||||
);
|
||||
|
||||
return new DashboardStats();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<HistoricalDataPoint>> GetDownloadHistoryAsync(int days = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new List<HistoricalDataPoint>();
|
||||
var endDate = DateTime.UtcNow;
|
||||
var startDate = endDate.AddDays(-days);
|
||||
|
||||
// Get download history from the database for added torrents
|
||||
var addedTorrents = await _torrentRepository.Query()
|
||||
.Where(t => t.AddedOn >= startDate && t.AddedOn <= endDate)
|
||||
.ToListAsync();
|
||||
|
||||
// Get completed torrents history
|
||||
var completedTorrents = await _torrentRepository.Query()
|
||||
.Where(t => t.CompletedOn.HasValue && t.CompletedOn.Value >= startDate && t.CompletedOn.Value <= endDate)
|
||||
.ToListAsync();
|
||||
|
||||
// Group by date added
|
||||
var groupedByAdded = addedTorrents
|
||||
.GroupBy(t => new DateTime(t.AddedOn.Year, t.AddedOn.Month, t.AddedOn.Day, 0, 0, 0, DateTimeKind.Utc))
|
||||
.Select(g => new {
|
||||
Date = g.Key,
|
||||
Count = g.Count(),
|
||||
TotalSize = g.Sum(t => t.TotalSize)
|
||||
})
|
||||
.OrderBy(g => g.Date)
|
||||
.ToList();
|
||||
|
||||
// Group by date completed
|
||||
var groupedByCompleted = completedTorrents
|
||||
.GroupBy(t => new DateTime(t.CompletedOn.Value.Year, t.CompletedOn.Value.Month, t.CompletedOn.Value.Day, 0, 0, 0, DateTimeKind.Utc))
|
||||
.Select(g => new {
|
||||
Date = g.Key,
|
||||
Count = g.Count(),
|
||||
TotalSize = g.Sum(t => t.TotalSize)
|
||||
})
|
||||
.OrderBy(g => g.Date)
|
||||
.ToList();
|
||||
|
||||
// Fill in missing dates
|
||||
for (var date = startDate; date <= endDate; date = date.AddDays(1))
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
var day = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
// Get data for this day
|
||||
var addedData = groupedByAdded.FirstOrDefault(g => g.Date == day);
|
||||
var completedData = groupedByCompleted.FirstOrDefault(g => g.Date == day);
|
||||
|
||||
// Create data point with added count and size
|
||||
result.Add(new HistoricalDataPoint
|
||||
{
|
||||
Date = day,
|
||||
Count = addedData?.Count ?? 0, // Use added count by default
|
||||
TotalSize = addedData?.TotalSize ?? 0,
|
||||
CompletedCount = completedData?.Count ?? 0,
|
||||
CompletedSize = completedData?.TotalSize ?? 0
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting download history");
|
||||
_loggingService.Log(
|
||||
LogLevel.Error,
|
||||
$"Error getting download history: {ex.Message}",
|
||||
"MetricsService",
|
||||
new Dictionary<string, string> {
|
||||
{ "ErrorMessage", ex.Message },
|
||||
{ "StackTrace", ex.StackTrace },
|
||||
{ "Days", days.ToString() }
|
||||
}
|
||||
);
|
||||
|
||||
return new List<HistoricalDataPoint>();
|
||||
["Error"] = ex.Message,
|
||||
["TotalTorrents"] = 0,
|
||||
["ActiveDownloads"] = 0,
|
||||
["SeedingTorrents"] = 0,
|
||||
["CompletedTorrents"] = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<CategoryStats>> GetCategoryStatsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get torrents with categories from database
|
||||
var dbTorrents = await _torrentRepository.Query()
|
||||
.Where(t => t.Category != null)
|
||||
.ToListAsync();
|
||||
|
||||
// Get torrents from Transmission for real-time data
|
||||
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync();
|
||||
|
||||
// Map Transmission torrents to dictionary by hash for quick lookup
|
||||
var transmissionTorrentsByHash = transmissionTorrents
|
||||
.Where(t => !string.IsNullOrEmpty(t.HashString))
|
||||
.ToDictionary(t => t.HashString, t => t);
|
||||
|
||||
// Create a list to store category stats with combined data
|
||||
var categoryStats = new Dictionary<string, CategoryStats>();
|
||||
|
||||
// Process database torrents first
|
||||
foreach (var torrent in dbTorrents)
|
||||
{
|
||||
var category = torrent.Category ?? "Uncategorized";
|
||||
|
||||
if (!categoryStats.TryGetValue(category, out var stats))
|
||||
{
|
||||
stats = new CategoryStats
|
||||
{
|
||||
Category = category,
|
||||
Count = 0,
|
||||
TotalSize = 0,
|
||||
ActiveCount = 0,
|
||||
CompletedCount = 0,
|
||||
DownloadSpeed = 0,
|
||||
UploadSpeed = 0
|
||||
};
|
||||
categoryStats[category] = stats;
|
||||
}
|
||||
|
||||
stats.Count++;
|
||||
stats.TotalSize += torrent.TotalSize;
|
||||
|
||||
// Check if this torrent is completed
|
||||
if (torrent.CompletedOn.HasValue)
|
||||
{
|
||||
stats.CompletedCount++;
|
||||
}
|
||||
|
||||
// Check if this torrent is active in Transmission
|
||||
if (transmissionTorrentsByHash.TryGetValue(torrent.Hash, out var transmissionTorrent))
|
||||
{
|
||||
stats.ActiveCount++;
|
||||
stats.DownloadSpeed += transmissionTorrent.DownloadSpeed;
|
||||
stats.UploadSpeed += transmissionTorrent.UploadSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
// Process any Transmission torrents that might not be in the database
|
||||
foreach (var torrent in transmissionTorrents)
|
||||
{
|
||||
// Skip if no hash or already processed
|
||||
if (string.IsNullOrEmpty(torrent.HashString) ||
|
||||
dbTorrents.Any(t => t.Hash == torrent.HashString))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var category = torrent.Category ?? "Uncategorized";
|
||||
|
||||
if (!categoryStats.TryGetValue(category, out var stats))
|
||||
{
|
||||
stats = new CategoryStats
|
||||
{
|
||||
Category = category,
|
||||
Count = 0,
|
||||
TotalSize = 0,
|
||||
ActiveCount = 0,
|
||||
CompletedCount = 0,
|
||||
DownloadSpeed = 0,
|
||||
UploadSpeed = 0
|
||||
};
|
||||
categoryStats[category] = stats;
|
||||
}
|
||||
|
||||
stats.Count++;
|
||||
stats.TotalSize += torrent.TotalSize;
|
||||
stats.ActiveCount++;
|
||||
stats.DownloadSpeed += torrent.DownloadSpeed;
|
||||
stats.UploadSpeed += torrent.UploadSpeed;
|
||||
|
||||
// Check if this torrent is completed
|
||||
if (torrent.IsFinished)
|
||||
{
|
||||
stats.CompletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the category stats ordered by count
|
||||
return categoryStats.Values
|
||||
.OrderByDescending(c => c.Count)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting category stats");
|
||||
_loggingService.Log(
|
||||
LogLevel.Error,
|
||||
$"Error getting category stats: {ex.Message}",
|
||||
"MetricsService",
|
||||
new Dictionary<string, string> {
|
||||
{ "ErrorMessage", ex.Message },
|
||||
{ "StackTrace", ex.StackTrace }
|
||||
}
|
||||
);
|
||||
|
||||
return new List<CategoryStats>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SystemStatus> GetSystemStatusAsync()
|
||||
{
|
||||
var config = _configService.GetConfiguration();
|
||||
|
||||
var status = new SystemStatus
|
||||
{
|
||||
TransmissionConnected = false,
|
||||
AutoDownloadEnabled = config.AutoDownloadEnabled,
|
||||
PostProcessingEnabled = config.PostProcessing.Enabled,
|
||||
EnabledFeeds = await _feedRepository.Query().Where(f => f.Enabled).CountAsync(),
|
||||
TotalFeeds = await _feedRepository.Query().CountAsync(),
|
||||
CheckIntervalMinutes = config.CheckIntervalMinutes,
|
||||
NotificationsEnabled = config.UserPreferences.NotificationsEnabled,
|
||||
DatabaseStatus = "Connected"
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Check database health by counting torrents
|
||||
var torrentCount = await _torrentRepository.Query().CountAsync();
|
||||
status.TorrentCount = torrentCount;
|
||||
|
||||
// Count torrents by status
|
||||
status.ActiveTorrentCount = await _torrentRepository.Query()
|
||||
.Where(t => t.Status == "downloading" || t.Status == "Downloading")
|
||||
.CountAsync();
|
||||
|
||||
status.CompletedTorrentCount = await _torrentRepository.Query()
|
||||
.Where(t => t.CompletedOn.HasValue)
|
||||
.CountAsync();
|
||||
|
||||
// Check feed items count
|
||||
status.FeedItemCount = await _feedItemRepository.Query().CountAsync();
|
||||
|
||||
// Check log entries count
|
||||
var logCount = await _logRepository.Query().CountAsync();
|
||||
status.LogEntryCount = logCount;
|
||||
|
||||
// Get database size estimate (rows * avg row size)
|
||||
long estimatedDbSize = (torrentCount * 1024) + (status.FeedItemCount * 512) + (logCount * 256);
|
||||
status.EstimatedDatabaseSizeBytes = estimatedDbSize;
|
||||
|
||||
// Try to connect to Transmission to check if it's available
|
||||
var torrents = await _transmissionClient.GetTorrentsAsync();
|
||||
|
||||
status.TransmissionConnected = true;
|
||||
status.TransmissionVersion = "Unknown"; // We don't have a way to get this info directly
|
||||
status.TransmissionTorrentCount = torrents.Count;
|
||||
|
||||
// Enhancement: Map any transmission torrents not in our database
|
||||
var dbTorrents = await _torrentRepository.Query().ToListAsync();
|
||||
var transmissionHashes = torrents.Select(t => t.HashString).ToHashSet();
|
||||
var dbHashes = dbTorrents.Select(t => t.Hash).ToHashSet();
|
||||
|
||||
status.OrphanedTorrentCount = transmissionHashes.Count(h => !dbHashes.Contains(h));
|
||||
status.StaleDbTorrentCount = dbHashes.Count(h => !transmissionHashes.Contains(h));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting system status");
|
||||
status.TransmissionConnected = false;
|
||||
status.LastErrorMessage = ex.Message;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estimates disk usage for torrents and available space
|
||||
/// </summary>
|
||||
public async Task<Dictionary<string, long>> EstimateDiskUsageAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get disk usage from both Transmission and database
|
||||
var transmissionTorrents = await _transmissionClient.GetTorrentsAsync();
|
||||
var dbTorrents = await _torrentRepository.Query().ToListAsync();
|
||||
|
||||
// Calculate total size from Transmission (most accurate for active torrents)
|
||||
long transmissionSize = transmissionTorrents.Sum(t => t.TotalSize);
|
||||
|
||||
// Add sizes from database for torrents not in Transmission (historical data)
|
||||
var transmissionHashes = transmissionTorrents.Select(t => t.HashString).ToHashSet();
|
||||
var historicalTorrents = dbTorrents.Where(t => !transmissionHashes.Contains(t.Hash));
|
||||
|
||||
long historicalSize = historicalTorrents.Sum(t => t.TotalSize);
|
||||
|
||||
// Also get estimated database size
|
||||
long databaseSize = await _torrentRepository.Query().CountAsync() * 1024 + // ~1KB per torrent
|
||||
await _feedItemRepository.Query().CountAsync() * 512 + // ~512B per feed item
|
||||
await _logRepository.Query().CountAsync() * 256; // ~256B per log entry
|
||||
// Get disk usage from torrents
|
||||
var torrents = await _transmissionClient.GetTorrentsAsync();
|
||||
long totalSize = torrents.Sum(t => t.TotalSize);
|
||||
|
||||
// Calculate available space in download directory
|
||||
string downloadDir = _configService.GetConfiguration().DownloadDirectory;
|
||||
@ -543,204 +106,56 @@ namespace TransmissionRssManager.Services
|
||||
|
||||
return new Dictionary<string, long>
|
||||
{
|
||||
["activeTorrentsSize"] = transmissionSize,
|
||||
["historicalTorrentsSize"] = historicalSize,
|
||||
["totalTorrentsSize"] = transmissionSize + historicalSize,
|
||||
["databaseSize"] = databaseSize,
|
||||
["activeTorrentsSize"] = totalSize,
|
||||
["availableSpace"] = availableSpace
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error estimating disk usage");
|
||||
_loggingService.Log(
|
||||
LogLevel.Error,
|
||||
$"Error estimating disk usage: {ex.Message}",
|
||||
"MetricsService",
|
||||
new Dictionary<string, string> {
|
||||
{ "ErrorMessage", ex.Message },
|
||||
{ "StackTrace", ex.StackTrace }
|
||||
}
|
||||
);
|
||||
|
||||
return new Dictionary<string, long>
|
||||
{
|
||||
["activeTorrentsSize"] = 0,
|
||||
["historicalTorrentsSize"] = 0,
|
||||
["totalTorrentsSize"] = 0,
|
||||
["databaseSize"] = 0,
|
||||
["availableSpace"] = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, double>> GetPerformanceMetricsAsync()
|
||||
/// <summary>
|
||||
/// Gets system status including Transmission connection state
|
||||
/// </summary>
|
||||
public async Task<Dictionary<string, object>> GetSystemStatusAsync()
|
||||
{
|
||||
var metrics = new Dictionary<string, double>();
|
||||
var config = _configService.GetConfiguration();
|
||||
|
||||
var status = new Dictionary<string, object>
|
||||
{
|
||||
["TransmissionConnected"] = false,
|
||||
["AutoDownloadEnabled"] = config.AutoDownloadEnabled,
|
||||
["PostProcessingEnabled"] = config.PostProcessing.Enabled,
|
||||
["CheckIntervalMinutes"] = config.CheckIntervalMinutes
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Calculate average time to complete downloads
|
||||
var completedTorrents = await _torrentRepository.Query()
|
||||
.Where(t => t.CompletedOn.HasValue)
|
||||
.ToListAsync();
|
||||
// Try to connect to Transmission to check if it's available
|
||||
var torrents = await _transmissionClient.GetTorrentsAsync();
|
||||
|
||||
if (completedTorrents.Any())
|
||||
{
|
||||
var avgCompletionTimeMinutes = completedTorrents
|
||||
.Where(t => t.AddedOn < t.CompletedOn)
|
||||
.Average(t => (t.CompletedOn.Value - t.AddedOn).TotalMinutes);
|
||||
|
||||
metrics["AvgCompletionTimeMinutes"] = Math.Round(avgCompletionTimeMinutes, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
metrics["AvgCompletionTimeMinutes"] = 0;
|
||||
}
|
||||
status["TransmissionConnected"] = true;
|
||||
status["TransmissionTorrentCount"] = torrents.Count;
|
||||
|
||||
// Calculate feed refresh performance
|
||||
var feeds = await _feedRepository.Query().ToListAsync();
|
||||
if (feeds.Any())
|
||||
{
|
||||
var avgItemsPerFeed = await _feedItemRepository.Query().CountAsync() / (double)feeds.Count;
|
||||
metrics["AvgItemsPerFeed"] = Math.Round(avgItemsPerFeed, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
metrics["AvgItemsPerFeed"] = 0;
|
||||
}
|
||||
|
||||
return metrics;
|
||||
// Count torrents by status
|
||||
status["ActiveTorrentCount"] = torrents.Count(t => t.Status == "Downloading");
|
||||
status["CompletedTorrentCount"] = torrents.Count(t => t.IsFinished);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting performance metrics");
|
||||
return new Dictionary<string, double>
|
||||
{
|
||||
["AvgCompletionTimeMinutes"] = 0,
|
||||
["AvgItemsPerFeed"] = 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DashboardStats
|
||||
{
|
||||
public int ActiveDownloads { get; set; }
|
||||
public int SeedingTorrents { get; set; }
|
||||
public int ActiveFeeds { get; set; }
|
||||
public int CompletedToday { get; set; }
|
||||
public int AddedToday { get; set; }
|
||||
public int FeedsCount { get; set; }
|
||||
public int MatchedCount { get; set; }
|
||||
public double DownloadSpeed { get; set; }
|
||||
public double UploadSpeed { get; set; }
|
||||
public long TotalDownloaded { get; set; }
|
||||
public long TotalUploaded { get; set; }
|
||||
}
|
||||
|
||||
public class HistoricalDataPoint
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public int Count { get; set; }
|
||||
public long TotalSize { get; set; }
|
||||
public int CompletedCount { get; set; }
|
||||
public long CompletedSize { get; set; }
|
||||
}
|
||||
|
||||
public class CategoryStats
|
||||
{
|
||||
public string Category { get; set; }
|
||||
public int Count { get; set; }
|
||||
public long TotalSize { get; set; }
|
||||
public int ActiveCount { get; set; }
|
||||
public int CompletedCount { get; set; }
|
||||
public double DownloadSpeed { get; set; }
|
||||
public double UploadSpeed { get; set; }
|
||||
}
|
||||
|
||||
public class SystemStatus
|
||||
{
|
||||
public bool TransmissionConnected { get; set; }
|
||||
public string TransmissionVersion { get; set; }
|
||||
public bool AutoDownloadEnabled { get; set; }
|
||||
public bool PostProcessingEnabled { get; set; }
|
||||
public int EnabledFeeds { get; set; }
|
||||
public int TotalFeeds { get; set; }
|
||||
public int CheckIntervalMinutes { get; set; }
|
||||
public bool NotificationsEnabled { get; set; }
|
||||
|
||||
// Database status
|
||||
public string DatabaseStatus { get; set; }
|
||||
public int TorrentCount { get; set; }
|
||||
public int ActiveTorrentCount { get; set; }
|
||||
public int CompletedTorrentCount { get; set; }
|
||||
public int FeedItemCount { get; set; }
|
||||
public int LogEntryCount { get; set; }
|
||||
public long EstimatedDatabaseSizeBytes { get; set; }
|
||||
|
||||
// Transmission status
|
||||
public int TransmissionTorrentCount { get; set; }
|
||||
|
||||
// Sync status
|
||||
public int OrphanedTorrentCount { get; set; } // Torrents in Transmission but not in database
|
||||
public int StaleDbTorrentCount { get; set; } // Torrents in database but not in Transmission
|
||||
|
||||
// For compatibility
|
||||
public string TranmissionVersion
|
||||
{
|
||||
get => TransmissionVersion;
|
||||
set => TransmissionVersion = value;
|
||||
}
|
||||
|
||||
// Error info
|
||||
public string LastErrorMessage { get; set; }
|
||||
}
|
||||
|
||||
public class MetricsBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<MetricsBackgroundService> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public MetricsBackgroundService(
|
||||
ILogger<MetricsBackgroundService> logger,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("Metrics background service started");
|
||||
|
||||
// Update metrics every minute
|
||||
var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var metricsService = scope.ServiceProvider.GetRequiredService<IMetricsService>();
|
||||
|
||||
// This just ensures the metrics are calculated and cached if needed
|
||||
await metricsService.GetDashboardStatsAsync();
|
||||
|
||||
_logger.LogDebug("Metrics updated");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Service is shutting down
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error updating metrics");
|
||||
}
|
||||
_logger.LogError(ex, "Error getting system status");
|
||||
status["TransmissionConnected"] = false;
|
||||
status["LastErrorMessage"] = ex.Message;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Metrics background service stopped");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user