New: Host Stats

This commit is contained in:
Qstick
2021-05-31 00:18:44 -04:00
parent 2ea05285a1
commit 6c8f037813
15 changed files with 132 additions and 28 deletions

View File

@@ -1,6 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import BarChart from 'Components/Chart/BarChart'; import BarChart from 'Components/Chart/BarChart';
import DoughnutChart from 'Components/Chart/DoughnutChart';
import StackedBarChart from 'Components/Chart/StackedBarChart'; import StackedBarChart from 'Components/Chart/StackedBarChart';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
@@ -87,6 +88,36 @@ function getUserAgentQueryData(indexerStats) {
return data; return data;
} }
function getHostGrabsData(indexerStats) {
const data = indexerStats.map((indexer) => {
return {
label: indexer.host ? indexer.host : 'Other',
value: indexer.numberOfGrabs
};
});
data.sort((a, b) => {
return b.value - a.value;
});
return data;
}
function getHostQueryData(indexerStats) {
const data = indexerStats.map((indexer) => {
return {
label: indexer.host ? indexer.host : 'Other',
value: indexer.numberOfQueries
};
});
data.sort((a, b) => {
return b.value - a.value;
});
return data;
}
function Stats(props) { function Stats(props) {
const { const {
item, item,
@@ -148,6 +179,20 @@ function Stats(props) {
horizontal={true} horizontal={true}
/> />
</div> </div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getHostQueryData(item.hosts)}
title='Total Host Queries'
horizontal={true}
/>
</div>
<div className={styles.halfWidthChart}>
<DoughnutChart
data={getHostGrabsData(item.hosts)}
title='Total Host Grabs'
horizontal={true}
/>
</div>
</div> </div>
} }
</PageContentBody> </PageContentBody>

View File

@@ -17,9 +17,9 @@ namespace NzbDrone.Core.Download
{ {
public interface IDownloadService public interface IDownloadService
{ {
void SendReportToClient(ReleaseInfo release, bool redirect); void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect);
Task<byte[]> DownloadReport(string link, int indexerId, string source, string title); Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title);
void RecordRedirect(string link, int indexerId, string source, string title); void RecordRedirect(string link, int indexerId, string source, string host, string title);
} }
public class DownloadService : IDownloadService public class DownloadService : IDownloadService
@@ -49,7 +49,7 @@ namespace NzbDrone.Core.Download
_logger = logger; _logger = logger;
} }
public void SendReportToClient(ReleaseInfo release, bool redirect) public void SendReportToClient(ReleaseInfo release, string source, string host, bool redirect)
{ {
var downloadTitle = release.Title; var downloadTitle = release.Title;
var downloadClient = _downloadClientProvider.GetDownloadClient(release.DownloadProtocol); var downloadClient = _downloadClientProvider.GetDownloadClient(release.DownloadProtocol);
@@ -79,13 +79,13 @@ namespace NzbDrone.Core.Download
catch (ReleaseUnavailableException) catch (ReleaseUnavailableException)
{ {
_logger.Trace("Release {0} no longer available on indexer.", release); _logger.Trace("Release {0} no longer available on indexer.", release);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, release.Source, release.Title, redirect)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, redirect));
throw; throw;
} }
catch (DownloadClientRejectedReleaseException) catch (DownloadClientRejectedReleaseException)
{ {
_logger.Trace("Release {0} rejected by download client, possible duplicate.", release); _logger.Trace("Release {0} rejected by download client, possible duplicate.", release);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, release.Source, release.Title, redirect)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, redirect));
throw; throw;
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)
@@ -100,17 +100,17 @@ namespace NzbDrone.Core.Download
_indexerStatusService.RecordFailure(release.IndexerId); _indexerStatusService.RecordFailure(release.IndexerId);
} }
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, release.Source, release.Title, redirect)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, false, source, host, release.Title, redirect));
throw; throw;
} }
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle); _logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, true, release.Source, release.Title, redirect)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(release.IndexerId, true, source, host, release.Title, redirect));
} }
public async Task<byte[]> DownloadReport(string link, int indexerId, string source, string title) public async Task<byte[]> DownloadReport(string link, int indexerId, string source, string host, string title)
{ {
var url = new Uri(link); var url = new Uri(link);
@@ -133,7 +133,7 @@ namespace NzbDrone.Core.Download
catch (ReleaseUnavailableException) catch (ReleaseUnavailableException)
{ {
_logger.Trace("Release {0} no longer available on indexer.", link); _logger.Trace("Release {0} no longer available on indexer.", link);
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, title)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title));
throw; throw;
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)
@@ -148,17 +148,17 @@ namespace NzbDrone.Core.Download
_indexerStatusService.RecordFailure(indexerId); _indexerStatusService.RecordFailure(indexerId);
} }
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, title)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title));
throw; throw;
} }
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, title)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, success, source, host, title));
return downloadedBytes; return downloadedBytes;
} }
public void RecordRedirect(string link, int indexerId, string source, string title) public void RecordRedirect(string link, int indexerId, string source, string host, string title)
{ {
_eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, true, source, title, true)); _eventAggregator.PublishEvent(new IndexerDownloadEvent(indexerId, true, source, host, title, true));
} }
} }
} }

View File

@@ -152,6 +152,7 @@ namespace NzbDrone.Core.History
history.Data.Add("QueryType", message.Query.SearchType ?? string.Empty); history.Data.Add("QueryType", message.Query.SearchType ?? string.Empty);
history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty); history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty);
history.Data.Add("Source", message.Query.Source ?? string.Empty); history.Data.Add("Source", message.Query.Source ?? string.Empty);
history.Data.Add("Host", message.Query.Host ?? string.Empty);
history.Data.Add("QueryResults", message.Results.HasValue ? message.Results.ToString() : null); history.Data.Add("QueryResults", message.Results.HasValue ? message.Results.ToString() : null);
_historyRepository.Insert(history); _historyRepository.Insert(history);
@@ -168,6 +169,7 @@ namespace NzbDrone.Core.History
}; };
history.Data.Add("Source", message.Source ?? string.Empty); history.Data.Add("Source", message.Source ?? string.Empty);
history.Data.Add("Host", message.Host ?? string.Empty);
history.Data.Add("GrabMethod", message.Redirect ? "Redirect" : "Proxy"); history.Data.Add("GrabMethod", message.Redirect ? "Redirect" : "Proxy");
history.Data.Add("Title", message.Title); history.Data.Add("Title", message.Title);

View File

@@ -19,6 +19,7 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public int? Limit { get; set; } public int? Limit { get; set; }
public int? Offset { get; set; } public int? Offset { get; set; }
public string Source { get; set; } public string Source { get; set; }
public string Host { get; set; }
public override string ToString() public override string ToString()
{ {

View File

@@ -26,6 +26,7 @@ namespace NzbDrone.Core.IndexerSearch
public string title { get; set; } public string title { get; set; }
public string configured { get; set; } public string configured { get; set; }
public string source { get; set; } public string source { get; set; }
public string host { get; set; }
public string server { get; set; } public string server { get; set; }
} }
} }

View File

@@ -128,6 +128,7 @@ namespace NzbDrone.Core.IndexerSearch
spec.Limit = query.limit; spec.Limit = query.limit;
spec.Offset = query.offset; spec.Offset = query.offset;
spec.Source = query.source; spec.Source = query.source;
spec.Host = query.host;
spec.IndexerIds = indexerIds; spec.IndexerIds = indexerIds;

View File

@@ -11,6 +11,10 @@ namespace NzbDrone.Core.IndexerStats
public int NumberOfGrabs { get; set; } public int NumberOfGrabs { get; set; }
public int NumberOfRssQueries { get; set; } public int NumberOfRssQueries { get; set; }
public int NumberOfAuthQueries { get; set; } public int NumberOfAuthQueries { get; set; }
public int NumberOfFailedQueries { get; set; }
public int NumberOfFailedGrabs { get; set; }
public int NumberOfFailedRssQueries { get; set; }
public int NumberOfFailedAuthQueries { get; set; }
} }
public class UserAgentStatistics : ResultSet public class UserAgentStatistics : ResultSet
@@ -18,7 +22,6 @@ namespace NzbDrone.Core.IndexerStats
public string UserAgent { get; set; } public string UserAgent { get; set; }
public int NumberOfQueries { get; set; } public int NumberOfQueries { get; set; }
public int NumberOfGrabs { get; set; } public int NumberOfGrabs { get; set; }
public int NumberOfRssQueries { get; set; }
} }
public class HostStatistics : ResultSet public class HostStatistics : ResultSet
@@ -26,6 +29,5 @@ namespace NzbDrone.Core.IndexerStats
public string Host { get; set; } public string Host { get; set; }
public int NumberOfQueries { get; set; } public int NumberOfQueries { get; set; }
public int NumberOfGrabs { get; set; } public int NumberOfGrabs { get; set; }
public int NumberOfRssQueries { get; set; }
} }
} }

View File

@@ -11,6 +11,7 @@ namespace NzbDrone.Core.IndexerStats
{ {
List<IndexerStatistics> IndexerStatistics(); List<IndexerStatistics> IndexerStatistics();
List<UserAgentStatistics> UserAgentStatistics(); List<UserAgentStatistics> UserAgentStatistics();
List<HostStatistics> HostStatistics();
} }
public class IndexerStatisticsRepository : IIndexerStatisticsRepository public class IndexerStatisticsRepository : IIndexerStatisticsRepository
@@ -36,6 +37,12 @@ namespace NzbDrone.Core.IndexerStats
return UserAgentQuery(UserAgentBuilder()); return UserAgentQuery(UserAgentBuilder());
} }
public List<HostStatistics> HostStatistics()
{
var time = DateTime.UtcNow;
return HostQuery(HostBuilder());
}
private List<IndexerStatistics> Query(SqlBuilder builder) private List<IndexerStatistics> Query(SqlBuilder builder)
{ {
var sql = builder.AddTemplate(_selectTemplate).LogQuery(); var sql = builder.AddTemplate(_selectTemplate).LogQuery();
@@ -56,22 +63,41 @@ namespace NzbDrone.Core.IndexerStats
} }
} }
private List<HostStatistics> HostQuery(SqlBuilder builder)
{
var sql = builder.AddTemplate(_selectTemplate).LogQuery();
using (var conn = _database.OpenConnection())
{
return conn.Query<HostStatistics>(sql.RawSql, sql.Parameters).ToList();
}
}
private SqlBuilder IndexerBuilder() => new SqlBuilder() private SqlBuilder IndexerBuilder() => new SqlBuilder()
.Select(@"Indexers.Id AS IndexerId, .Select(@"Indexers.Id AS IndexerId,
Indexers.Name AS IndexerName, Indexers.Name AS IndexerName,
SUM(CASE WHEN EventType == 2 then 1 else 0 end) AS NumberOfQueries, SUM(CASE WHEN EventType == 2 then 1 else 0 end) AS NumberOfQueries,
SUM(CASE WHEN EventType == 2 AND Successful == 0 then 1 else 0 end) AS NumberOfFailedQueries,
SUM(CASE WHEN EventType == 3 then 1 else 0 end) AS NumberOfRssQueries, SUM(CASE WHEN EventType == 3 then 1 else 0 end) AS NumberOfRssQueries,
SUM(CASE WHEN EventType == 3 AND Successful == 0 then 1 else 0 end) AS NumberOfFailedRssQueries,
SUM(CASE WHEN EventType == 4 then 1 else 0 end) AS NumberOfAuthQueries, SUM(CASE WHEN EventType == 4 then 1 else 0 end) AS NumberOfAuthQueries,
SUM(CASE WHEN EventType == 4 AND Successful == 0 then 1 else 0 end) AS NumberOfFailedAuthQueries,
SUM(CASE WHEN EventType == 1 then 1 else 0 end) AS NumberOfGrabs, SUM(CASE WHEN EventType == 1 then 1 else 0 end) AS NumberOfGrabs,
SUM(CASE WHEN EventType == 1 AND Successful == 0 then 1 else 0 end) AS NumberOfFailedGrabs,
AVG(json_extract(History.Data,'$.elapsedTime')) AS AverageResponseTime") AVG(json_extract(History.Data,'$.elapsedTime')) AS AverageResponseTime")
.Join<History.History, IndexerDefinition>((t, r) => t.IndexerId == r.Id) .Join<History.History, IndexerDefinition>((t, r) => t.IndexerId == r.Id)
.GroupBy<IndexerDefinition>(x => x.Id); .GroupBy<IndexerDefinition>(x => x.Id);
private SqlBuilder UserAgentBuilder() => new SqlBuilder() private SqlBuilder UserAgentBuilder() => new SqlBuilder()
.Select(@"json_extract(History.Data,'$.source') AS UserAgent, .Select(@"json_extract(History.Data,'$.source') AS UserAgent,
SUM(CASE WHEN EventType == 2 then 1 else 0 end) AS NumberOfQueries, SUM(CASE WHEN EventType == 2 OR EventType == 3 then 1 else 0 end) AS NumberOfQueries,
SUM(CASE WHEN EventType == 1 then 1 else 0 end) AS NumberOfGrabs, SUM(CASE WHEN EventType == 1 then 1 else 0 end) AS NumberOfGrabs")
SUM(CASE WHEN EventType == 3 then 1 else 0 end) AS NumberOfRssQueries")
.GroupBy("UserAgent"); .GroupBy("UserAgent");
private SqlBuilder HostBuilder() => new SqlBuilder()
.Select(@"json_extract(History.Data,'$.host') AS Host,
SUM(CASE WHEN EventType == 2 OR EventType == 3 then 1 else 0 end) AS NumberOfQueries,
SUM(CASE WHEN EventType == 1 then 1 else 0 end) AS NumberOfGrabs")
.GroupBy("Host");
} }
} }

View File

@@ -7,6 +7,7 @@ namespace NzbDrone.Core.IndexerStats
{ {
List<IndexerStatistics> IndexerStatistics(); List<IndexerStatistics> IndexerStatistics();
List<UserAgentStatistics> UserAgentStatistics(); List<UserAgentStatistics> UserAgentStatistics();
List<HostStatistics> HostStatistics();
} }
public class IndexerStatisticsService : IIndexerStatisticsService public class IndexerStatisticsService : IIndexerStatisticsService
@@ -20,16 +21,23 @@ namespace NzbDrone.Core.IndexerStats
public List<IndexerStatistics> IndexerStatistics() public List<IndexerStatistics> IndexerStatistics()
{ {
var seasonStatistics = _indexerStatisticsRepository.IndexerStatistics(); var indexerStatistics = _indexerStatisticsRepository.IndexerStatistics();
return seasonStatistics.ToList(); return indexerStatistics.ToList();
} }
public List<UserAgentStatistics> UserAgentStatistics() public List<UserAgentStatistics> UserAgentStatistics()
{ {
var seasonStatistics = _indexerStatisticsRepository.UserAgentStatistics(); var userAgentStatistics = _indexerStatisticsRepository.UserAgentStatistics();
return seasonStatistics.ToList(); return userAgentStatistics.ToList();
}
public List<HostStatistics> HostStatistics()
{
var hostStatistics = _indexerStatisticsRepository.HostStatistics();
return hostStatistics.ToList();
} }
} }
} }

View File

@@ -7,14 +7,16 @@ namespace NzbDrone.Core.Indexers.Events
public int IndexerId { get; set; } public int IndexerId { get; set; }
public bool Successful { get; set; } public bool Successful { get; set; }
public string Source { get; set; } public string Source { get; set; }
public string Host { get; set; }
public string Title { get; set; } public string Title { get; set; }
public bool Redirect { get; set; } public bool Redirect { get; set; }
public IndexerDownloadEvent(int indexerId, bool successful, string source, string title, bool redirect = false) public IndexerDownloadEvent(int indexerId, bool successful, string source, string host, string title, bool redirect = false)
{ {
IndexerId = indexerId; IndexerId = indexerId;
Successful = successful; Successful = successful;
Source = source; Source = source;
Host = host;
Title = title; Title = title;
Redirect = redirect; Redirect = redirect;
} }

View File

@@ -21,6 +21,7 @@ namespace Prowlarr.Api.V1.Indexers
{ {
Indexers = _indexerStatisticsService.IndexerStatistics(), Indexers = _indexerStatisticsService.IndexerStatistics(),
UserAgents = _indexerStatisticsService.UserAgentStatistics(), UserAgents = _indexerStatisticsService.UserAgentStatistics(),
Hosts = _indexerStatisticsService.HostStatistics()
}; };
return indexerResource; return indexerResource;

View File

@@ -8,5 +8,6 @@ namespace Prowlarr.Api.V1.Indexers
{ {
public List<IndexerStatistics> Indexers { get; set; } public List<IndexerStatistics> Indexers { get; set; }
public List<UserAgentStatistics> UserAgents { get; set; } public List<UserAgentStatistics> UserAgents { get; set; }
public List<HostStatistics> Hosts { get; set; }
} }
} }

View File

@@ -6,10 +6,10 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch; using NzbDrone.Core.IndexerSearch;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using Prowlarr.Http.Extensions; using Prowlarr.Http.Extensions;
using Prowlarr.Http.REST; using Prowlarr.Http.REST;
@@ -44,6 +44,7 @@ namespace NzbDrone.Api.V1.Indexers
var requestType = request.t; var requestType = request.t;
request.source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]); request.source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
request.server = Request.GetServerUrl(); request.server = Request.GetServerUrl();
request.host = Request.GetHostName();
if (requestType.IsNullOrWhiteSpace()) if (requestType.IsNullOrWhiteSpace())
{ {
@@ -107,18 +108,19 @@ namespace NzbDrone.Api.V1.Indexers
} }
var source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]); var source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
var host = Request.GetHostName();
var unprotectedlLink = "https://superbits.org/api/v1/torrents/download/797354"; var unprotectedlLink = "https://superbits.org/api/v1/torrents/download/797354";
// If Indexer is set to download via Redirect then just redirect to the link // If Indexer is set to download via Redirect then just redirect to the link
if (indexer.SupportsRedirect && indexerDef.Redirect) if (indexer.SupportsRedirect && indexerDef.Redirect)
{ {
_downloadService.RecordRedirect(unprotectedlLink, id, source, file); _downloadService.RecordRedirect(unprotectedlLink, id, source, host, file);
return RedirectPermanent(unprotectedlLink); return RedirectPermanent(unprotectedlLink);
} }
var downloadBytes = Array.Empty<byte>(); var downloadBytes = Array.Empty<byte>();
downloadBytes = await _downloadService.DownloadReport(unprotectedlLink, id, source, file); downloadBytes = await _downloadService.DownloadReport(unprotectedlLink, id, source, host, file);
// handle magnet URLs // handle magnet URLs
if (downloadBytes.Length >= 7 if (downloadBytes.Length >= 7

View File

@@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc;
using NLog; using NLog;
using NzbDrone.Common.Cache; using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers;
@@ -58,10 +59,12 @@ namespace Prowlarr.Api.V1.Search
var releaseInfo = _remoteReleaseCache.Find(GetCacheKey(release)); var releaseInfo = _remoteReleaseCache.Find(GetCacheKey(release));
var indexerDef = _indexerFactory.Get(release.IndexerId); var indexerDef = _indexerFactory.Get(release.IndexerId);
var source = UserAgentParser.ParseSource(Request.Headers["User-Agent"]);
var host = Request.GetHostName();
try try
{ {
_downloadService.SendReportToClient(releaseInfo, indexerDef.Redirect); _downloadService.SendReportToClient(releaseInfo, source, host, indexerDef.Redirect);
} }
catch (ReleaseDownloadException ex) catch (ReleaseDownloadException ex)
{ {

View File

@@ -161,6 +161,15 @@ namespace Prowlarr.Http.Extensions
return remoteAddress; return remoteAddress;
} }
public static string GetHostName(this HttpRequest request)
{
string ip = request.GetRemoteIP();
IPAddress myIP = IPAddress.Parse(ip);
IPHostEntry getIPHost = Dns.GetHostEntry(myIP);
List<string> compName = getIPHost.HostName.ToString().Split('.').ToList();
return compName.First();
}
public static string GetServerUrl(this HttpRequest request) public static string GetServerUrl(this HttpRequest request)
{ {
var scheme = request.Scheme; var scheme = request.Scheme;