From 361b6ba74ab0a24ab3caf7d375e9316f36a8d7a2 Mon Sep 17 00:00:00 2001 From: mhdzumair Date: Thu, 9 Jan 2025 23:46:18 +0530 Subject: [PATCH] Add charts for top torrent uploaders and weekly top uploader data & improve debrid cache & torrent source charts Introduced new visualizations to display the top 20 torrent uploaders and their weekly activity. Enhanced the styling for metrics sections and updated backend logic to support these additions, ensuring cleaner and more focused data retrieval. --- metrics/redis_metrics.py | 44 ++---- metrics/routes.py | 64 +++++++++ resources/css/metrics.css | 76 +++++++--- resources/html/metrics.html | 41 +++--- resources/js/metrics.js | 274 ++++++++++++++++++++++++++---------- 5 files changed, 351 insertions(+), 148 deletions(-) diff --git a/metrics/redis_metrics.py b/metrics/redis_metrics.py index b35d96a..bcb61b2 100644 --- a/metrics/redis_metrics.py +++ b/metrics/redis_metrics.py @@ -110,14 +110,10 @@ async def get_redis_metrics() -> Dict[str, Any]: async def get_debrid_cache_metrics() -> Dict[str, Any]: """ - Get detailed metrics about debrid cache usage. + Get simplified metrics about debrid cache usage, focusing only on cached torrents per service. """ - metrics = { - "timestamp": datetime.now(tz=timezone.utc).isoformat(), - "total_memory_usage": 0, - "total_cached_torrents": 0, - "services": {}, - } + metrics = {"timestamp": datetime.now(tz=timezone.utc).isoformat(), "services": {}} + debrid_services = [ "alldebrid", "debridlink", @@ -132,37 +128,21 @@ async def get_debrid_cache_metrics() -> Dict[str, Any]: ] try: - for service in debrid_services: cache_key = f"debrid_cache:{service}" - - # Get various metrics for each service cache_size = await REDIS_ASYNC_CLIENT.hlen(cache_key) - memory_usage = await REDIS_ASYNC_CLIENT.memory_usage(cache_key) or 0 - metrics["services"][service] = { - "cached_torrents": cache_size, - "memory_usage": memory_usage, - } + if cache_size > 0: # Only include services with cached torrents + metrics["services"][service] = {"cached_torrents": cache_size} - # Update totals - metrics["total_cached_torrents"] += cache_size - metrics["total_memory_usage"] += memory_usage - - # Add human readable total memory - metrics["total_memory_usage_human"] = humanize.naturalsize( - metrics["total_memory_usage"] - ) - - # Calculate which service has most cached torrents - if metrics["services"]: - most_cached = max( - metrics["services"].items(), key=lambda x: x[1]["cached_torrents"] + # Sort services by cache size + metrics["services"] = dict( + sorted( + metrics["services"].items(), + key=lambda x: x[1]["cached_torrents"], + reverse=True, ) - metrics["most_used_service"] = { - "name": most_cached[0], - "cached_count": most_cached[1]["cached_torrents"], - } + ) return metrics except Exception as e: diff --git a/metrics/routes.py b/metrics/routes.py index c8f2ec0..3842778 100644 --- a/metrics/routes.py +++ b/metrics/routes.py @@ -1,4 +1,5 @@ import asyncio +from datetime import datetime, timezone, timedelta import humanize from fastapi import APIRouter, Request, Response @@ -65,6 +66,7 @@ async def get_torrents_by_sources(response: Response): [ {"$group": {"_id": "$source", "count": {"$sum": 1}}}, {"$sort": {"count": -1}}, + {"$limit": 20}, # Limit to top 20 sources ] ).to_list() torrent_sources = [ @@ -151,3 +153,65 @@ async def debrid_cache_metrics(): Returns statistics about cache size, memory usage, and usage patterns per service. """ return await get_debrid_cache_metrics() + + +@metrics_router.get("/torrents/uploaders", tags=["metrics"]) +async def get_torrents_by_uploaders(response: Response): + response.headers.update(const.NO_CACHE_HEADERS) + results = await TorrentStreams.aggregate( + [ + {"$match": {"uploader": {"$ne": None}}}, + {"$group": {"_id": "$uploader", "count": {"$sum": 1}}}, + {"$sort": {"count": -1}}, + {"$limit": 20}, # Limit to top 20 uploaders + ] + ).to_list() + + uploader_stats = [ + {"name": stat["_id"] if stat["_id"] else "Unknown", "count": stat["count"]} + for stat in results + ] + return uploader_stats + + +@metrics_router.get("/torrents/uploaders/weekly", tags=["metrics"]) +async def get_weekly_top_uploaders(response: Response): + response.headers.update(const.NO_CACHE_HEADERS) + + # Calculate the start of the current week (Monday) + today = datetime.now(tz=timezone.utc) + start_of_week = today - timedelta(days=today.weekday()) + start_of_week = start_of_week.replace(hour=0, minute=0, second=0, microsecond=0) + + results = await TorrentStreams.aggregate( + [ + { + "$match": { + "uploader": {"$ne": None}, + "created_at": {"$gte": start_of_week}, + } + }, + { + "$group": { + "_id": "$uploader", + "count": {"$sum": 1}, + "latest_upload": {"$max": "$created_at"}, + } + }, + {"$sort": {"count": -1}}, + {"$limit": 20}, + ] + ).to_list() + + uploaders_stats = [ + { + "name": stat["_id"], + "count": stat["count"], + "latest_upload": ( + stat["latest_upload"].isoformat() if stat["latest_upload"] else None + ), + } + for stat in results + ] + + return {"week_start": start_of_week.isoformat(), "uploaders": uploaders_stats} diff --git a/resources/css/metrics.css b/resources/css/metrics.css index 94d3150..39ba3c9 100644 --- a/resources/css/metrics.css +++ b/resources/css/metrics.css @@ -13,6 +13,14 @@ body, html { overflow-y: auto; } +h4 { + color: #ffffff; + font-weight: 600; + margin-bottom: 1.5rem; + border-bottom: 2px solid rgba(74, 71, 163, 0.5); + padding-bottom: 0.5rem; +} + .config-container { margin: 2% auto; padding: 2%; @@ -38,7 +46,37 @@ body, html { } .chart-section { - margin-bottom: 2rem; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + padding: 20px; + margin-bottom: 30px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; +} + +.chart-section:hover { + transform: translateY(-5px); +} + +.date-info { + background: rgba(129,87,208,0.6); + padding: 8px 16px; + border-radius: 4px; + text-align: center; + font-size: 0.9rem; +} + +.chart-wrapper { + position: relative; + width: 100%; + height: 400px; + margin: 20px 0; +} + +.chart-wrapper canvas { + position: absolute; + width: 100% !important; + height: 100% !important; } .chart-container { @@ -49,19 +87,6 @@ body, html { position: relative; } -.chart-wrapper { - position: relative; - width: 100%; - height: 0; - padding-bottom: 50%; -} - -.chart-wrapper canvas { - position: absolute; - width: 100%; - height: 100%; -} - .logo { max-width: 300px; display: block; @@ -69,9 +94,14 @@ body, html { } .card { - background: #4a47a3; - position: relative; - text-align: center; + background: linear-gradient(145deg, #4a47a3, #2d2a6a); + border: none; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); } .card-text { @@ -178,6 +208,18 @@ body, html { @media (max-width: 768px) { + .chart-wrapper { + height: 300px; + } + + .chart-section { + padding: 15px; + margin-bottom: 20px; + } + + .card-text { + font-size: 1.2rem; + } .config-container { padding: 10px; } diff --git a/resources/html/metrics.html b/resources/html/metrics.html index d0e6795..8028dd3 100644 --- a/resources/html/metrics.html +++ b/resources/html/metrics.html @@ -48,7 +48,7 @@
-

Torrent Count By Sources

+

Top 20 Torrents Sources

@@ -56,30 +56,27 @@
-

Debrid Cache Statistics

-
-
-
-
-
Total Cached Torrents Count (Not Unique)
-

-
-
-
-
-
-
-
-
Total Cache Memory Usage
-

-
-
-
-
+

Top 20 Torrents Uploaders

+
+ +
+
+ +
+

Top 20 Uploaders This Week

+
+
+ +
+
+
+ +
+

Debrid Cache Statistics

-
Cache Distribution
+
Cached Torrents by Service
diff --git a/resources/js/metrics.js b/resources/js/metrics.js index 54b6159..6a4b511 100644 --- a/resources/js/metrics.js +++ b/resources/js/metrics.js @@ -230,110 +230,228 @@ document.addEventListener('DOMContentLoaded', function () { }); }; - const renderDebridCacheMetrics = async () => { - const data = await fetchData('/metrics/debrid-cache'); + const renderTorrentUploadersChart = async () => { + const data = await fetchData('/metrics/torrents/uploaders'); + removeLoadingElement('torrentUploadersSkeleton'); + const ctx = document.getElementById('torrentUploadersChart').getContext('2d'); - // Remove loading skeletons - removeLoadingElement('debridTotalsSkeleton'); - removeLoadingElement('debridMemorySkeleton'); - removeLoadingElement('debridChartSkeleton'); - - // Update total values - const totalCachedTorrents = document.getElementById('totalCachedTorrentsValue'); - totalCachedTorrents.textContent = data.total_cached_torrents.toLocaleString(); - - const totalMemoryUsage = document.getElementById('totalCacheMemoryValue'); - totalMemoryUsage.textContent = data.total_memory_usage_human; - - // Create the stacked bar chart for service comparison - const ctx = document.getElementById('debridCacheChart').getContext('2d'); - - // Prepare data for the chart - const services = Object.keys(data.services); - const cachedTorrents = services.map(service => data.services[service].cached_torrents); - const memoryUsages = services.map(service => data.services[service].memory_usage / (1024 * 1024)); // Convert to MB + const sortedData = data.sort((a, b) => b.count - a.count); new Chart(ctx, { type: 'bar', data: { - labels: services, - datasets: [ - { - label: 'Cached Torrents', - data: cachedTorrents, - backgroundColor: 'rgba(54, 162, 235, 0.6)', - borderColor: 'rgba(54, 162, 235, 1)', - borderWidth: 1, - yAxisID: 'y' - }, - { - label: 'Memory Usage (MB)', - data: memoryUsages, - backgroundColor: 'rgba(255, 99, 132, 0.6)', - borderColor: 'rgba(255, 99, 132, 1)', - borderWidth: 1, - yAxisID: 'y1' - } - ] + labels: sortedData.map(item => item.name), + datasets: [{ + data: sortedData.map(item => item.count), + backgroundColor: 'rgba(75, 192, 192, 0.6)', + borderColor: 'rgba(75, 192, 192, 1)', + borderWidth: 1 + }] }, options: { + indexAxis: 'y', responsive: true, maintainAspectRatio: false, - interaction: { - mode: 'index', - intersect: false, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + return `${context.raw.toLocaleString()} Torrents`; + } + } + }, + datalabels: { + color: '#fff', + anchor: 'end', + align: 'end', + formatter: (value) => value.toLocaleString(), + font: { + weight: 'bold' + } + } }, scales: { x: { - ticks: { - color: '#fff' - } - }, - y: { - type: 'linear', - display: true, - position: 'left', - title: { - display: true, - text: 'Cached Torrents', - color: '#fff' + beginAtZero: true, + grid: { + color: 'rgba(255, 255, 255, 0.1)' }, ticks: { color: '#fff' } }, - y1: { - type: 'linear', - display: true, - position: 'right', + y: { + grid: { + display: false + }, + ticks: { + color: '#fff' + } + } + } + } + }); + }; + + const renderWeeklyUploadersChart = async () => { + const data = await fetchData('/metrics/torrents/uploaders/weekly'); + removeLoadingElement('weeklyUploadersSkeleton'); + + // Update date range display + const weekStart = new Date(data.week_start); + const weekEnd = new Date(weekStart); + weekEnd.setDate(weekEnd.getDate() + 6); + + const dateRangeElement = document.getElementById('weekDateRange'); + dateRangeElement.innerHTML = ` + + ${weekStart.toLocaleDateString()} - ${weekEnd.toLocaleDateString()} + + `; + + const ctx = document.getElementById('weeklyUploadersChart').getContext('2d'); + + new Chart(ctx, { + type: 'bar', + data: { + labels: data.uploaders.map(uploader => uploader.name), + datasets: [{ + data: data.uploaders.map(uploader => uploader.count), + backgroundColor: 'rgba(122,76,204,0.6)', + borderColor: 'rgba(129,87,208,0.6)', + borderWidth: 1 + }] + }, + options: { + indexAxis: 'y', + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + const uploader = data.uploaders[context.dataIndex]; + const lastUpload = new Date(uploader.latest_upload); + return [ + `Torrents: ${context.raw.toLocaleString()}`, + `Last Upload: ${lastUpload.toLocaleString()}` + ]; + } + } + }, + datalabels: { + color: '#fff', + anchor: 'end', + align: 'end', + formatter: (value) => value.toLocaleString(), + font: { + weight: 'bold' + } + } + }, + scales: { + x: { + beginAtZero: true, + grid: { + color: 'rgba(255, 255, 255, 0.1)' + }, + ticks: { + color: '#fff', + callback: function (value) { + return value.toLocaleString(); + } + } + }, + y: { + grid: { + display: false + }, + ticks: { + color: '#fff', + font: { + size: 12 + } + } + } + } + } + }); + }; + + const renderDebridCacheMetrics = async () => { + const data = await fetchData('/metrics/debrid-cache'); + removeLoadingElement('debridChartSkeleton'); + + // Create the bar chart for service comparison + const ctx = document.getElementById('debridCacheChart').getContext('2d'); + + // Prepare data for the chart + const services = Object.keys(data.services); + const cachedTorrents = services.map(service => data.services[service].cached_torrents); + + new Chart(ctx, { + type: 'bar', + data: { + labels: services, + datasets: [{ + label: 'Cached Torrents', + data: cachedTorrents, + backgroundColor: 'rgba(54, 162, 235, 0.6)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function (context) { + return `Cached Torrents: ${context.raw.toLocaleString()}`; + } + } + }, + datalabels: { + color: '#fff', + anchor: 'end', + align: 'end', + formatter: (value) => value.toLocaleString(), + font: { + weight: 'bold' + } + } + }, + scales: { + y: { + beginAtZero: true, title: { display: true, - text: 'Memory Usage (MB)', + text: 'Number of Cached Torrents', color: '#fff' }, ticks: { color: '#fff' }, grid: { - drawOnChartArea: false - } - } - }, - plugins: { - legend: { - labels: { - color: '#fff' + color: 'rgba(255, 255, 255, 0.1)' } }, - tooltip: { - callbacks: { - label: function (context) { - if (context.datasetIndex === 0) { - return `Cached Torrents: ${context.raw.toLocaleString()}`; - } else { - return `Memory Usage: ${context.raw.toFixed(2)} MB`; - } - } + x: { + ticks: { + color: '#fff' + }, + grid: { + display: false } } } @@ -346,6 +464,8 @@ document.addEventListener('DOMContentLoaded', function () { renderMetadataCountsChart(); renderTotalTorrentsCount(); renderTorrentSourcesChart(); + renderTorrentUploadersChart(); + renderWeeklyUploadersChart(); renderDebridCacheMetrics(); };