Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Taloth Saldono | a19776553e |
|
@ -230,6 +230,7 @@
|
||||||
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
||||||
<Compile Include="System\Backup\BackupModule.cs" />
|
<Compile Include="System\Backup\BackupModule.cs" />
|
||||||
<Compile Include="System\Backup\BackupResource.cs" />
|
<Compile Include="System\Backup\BackupResource.cs" />
|
||||||
|
<Compile Include="System\Statistics\StatisticsModule.cs" />
|
||||||
<Compile Include="System\Tasks\TaskModule.cs" />
|
<Compile Include="System\Tasks\TaskModule.cs" />
|
||||||
<Compile Include="System\Tasks\TaskResource.cs" />
|
<Compile Include="System\Tasks\TaskResource.cs" />
|
||||||
<Compile Include="System\SystemModule.cs" />
|
<Compile Include="System\SystemModule.cs" />
|
||||||
|
@ -278,4 +279,4 @@
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Nancy;
|
||||||
|
using Nancy.Routing;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Core.Lifecycle;
|
||||||
|
using NzbDrone.Core.Statistics;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.System
|
||||||
|
{
|
||||||
|
public class StatisticsModule : NzbDroneApiModule
|
||||||
|
{
|
||||||
|
private readonly IStatisticsService _statisticsService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public StatisticsModule(IStatisticsService statisticsService, Logger logger)
|
||||||
|
: base("system/statistics")
|
||||||
|
{
|
||||||
|
_statisticsService = statisticsService;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
Get["/"] = x => GetGlobalStatistics();
|
||||||
|
Get["/indexer"] = x => GetIndexerStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response GetGlobalStatistics()
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Generated = DateTime.UtcNow,
|
||||||
|
Uptime = GetUpTime(),
|
||||||
|
History = _statisticsService.GetGlobalStatistics()
|
||||||
|
}.AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response GetIndexerStatistics()
|
||||||
|
{
|
||||||
|
var stats = _statisticsService.GetIndexerStatistics();
|
||||||
|
|
||||||
|
return stats.AsResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan? GetUpTime()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return DateTime.Now - global::System.Diagnostics.Process.GetCurrentProcess().StartTime;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.DebugException("Failed to get uptime", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -954,6 +954,9 @@
|
||||||
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
<Compile Include="SeriesStats\SeriesStatistics.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
<Compile Include="SeriesStats\SeriesStatisticsRepository.cs" />
|
||||||
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
|
<Compile Include="SeriesStats\SeriesStatisticsService.cs" />
|
||||||
|
<Compile Include="Statistics\HistoryStatistics.cs" />
|
||||||
|
<Compile Include="Statistics\StatisticsGrouping.cs" />
|
||||||
|
<Compile Include="Statistics\StatisticsService.cs" />
|
||||||
<Compile Include="Tags\Tag.cs" />
|
<Compile Include="Tags\Tag.cs" />
|
||||||
<Compile Include="Tags\TagRepository.cs" />
|
<Compile Include="Tags\TagRepository.cs" />
|
||||||
<Compile Include="Tags\TagService.cs" />
|
<Compile Include="Tags\TagService.cs" />
|
||||||
|
@ -1118,4 +1121,4 @@
|
||||||
<Target Name="AfterBuild">
|
<Target Name="AfterBuild">
|
||||||
</Target>
|
</Target>
|
||||||
-->
|
-->
|
||||||
</Project>
|
</Project>
|
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Statistics
|
||||||
|
{
|
||||||
|
public class HistoryStatistics
|
||||||
|
{
|
||||||
|
public int Grabs { get; set; }
|
||||||
|
public int Replaced { get; set; }
|
||||||
|
public int Failed { get; set; }
|
||||||
|
public int Imported { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Statistics
|
||||||
|
{
|
||||||
|
public class StatisticsGrouping<T> where T : new()
|
||||||
|
{
|
||||||
|
public T LastWeek { get; set; }
|
||||||
|
public T LastMonth { get; set; }
|
||||||
|
public T AllTime { get; set; }
|
||||||
|
|
||||||
|
public StatisticsGrouping()
|
||||||
|
{
|
||||||
|
LastWeek = new T();
|
||||||
|
LastMonth = new T();
|
||||||
|
AllTime = new T();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply(DateTime date, Action<T> applyAction)
|
||||||
|
{
|
||||||
|
var elapsed = DateTime.UtcNow - date.ToUniversalTime();
|
||||||
|
|
||||||
|
if (elapsed < TimeSpan.FromDays(7))
|
||||||
|
{
|
||||||
|
applyAction(LastWeek);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed < TimeSpan.FromDays(7 * 4))
|
||||||
|
{
|
||||||
|
applyAction(LastMonth);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAction(AllTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.History;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Statistics
|
||||||
|
{
|
||||||
|
public interface IStatisticsService
|
||||||
|
{
|
||||||
|
StatisticsGrouping<HistoryStatistics> GetGlobalStatistics();
|
||||||
|
Dictionary<string, StatisticsGrouping<HistoryStatistics>> GetIndexerStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatisticsService : IStatisticsService
|
||||||
|
{
|
||||||
|
private readonly IHistoryRepository _historyRepository;
|
||||||
|
|
||||||
|
public StatisticsService(IHistoryRepository historyRepository)
|
||||||
|
{
|
||||||
|
_historyRepository = historyRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatisticsGrouping<HistoryStatistics> GetGlobalStatistics()
|
||||||
|
{
|
||||||
|
var stats = new StatisticsGrouping<HistoryStatistics>();
|
||||||
|
|
||||||
|
foreach (var group in GetIndexerStatistics().Values)
|
||||||
|
{
|
||||||
|
stats.LastWeek.Grabs += group.LastWeek.Grabs;
|
||||||
|
stats.LastWeek.Replaced += group.LastWeek.Replaced;
|
||||||
|
stats.LastWeek.Failed += group.LastWeek.Failed;
|
||||||
|
stats.LastWeek.Imported += group.LastWeek.Imported;
|
||||||
|
|
||||||
|
stats.LastMonth.Grabs += group.LastMonth.Grabs;
|
||||||
|
stats.LastMonth.Replaced += group.LastMonth.Replaced;
|
||||||
|
stats.LastMonth.Failed += group.LastMonth.Failed;
|
||||||
|
stats.LastMonth.Imported += group.LastMonth.Imported;
|
||||||
|
|
||||||
|
stats.AllTime.Grabs += group.AllTime.Grabs;
|
||||||
|
stats.AllTime.Replaced += group.AllTime.Replaced;
|
||||||
|
stats.AllTime.Failed += group.AllTime.Failed;
|
||||||
|
stats.AllTime.Imported += group.AllTime.Imported;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, StatisticsGrouping<HistoryStatistics>> GetIndexerStatistics()
|
||||||
|
{
|
||||||
|
var all = _historyRepository.All().ToArray();
|
||||||
|
|
||||||
|
var stats = new Dictionary<string, StatisticsGrouping<HistoryStatistics>>();
|
||||||
|
stats[string.Empty] = new StatisticsGrouping<HistoryStatistics>();
|
||||||
|
|
||||||
|
var groupedByEpisode = all.GroupBy(v => v.EpisodeId).ToArray();
|
||||||
|
|
||||||
|
foreach (var episode in groupedByEpisode)
|
||||||
|
{
|
||||||
|
var sortedEvents = episode.OrderBy(v => v.DownloadId)
|
||||||
|
.ThenBy(v => v.Date)
|
||||||
|
.ThenBy(v => v.Id)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var lastEvent = HistoryEventType.Unknown;
|
||||||
|
string grabIndexer = null;
|
||||||
|
string importIndexer = null;
|
||||||
|
|
||||||
|
foreach (var historyEvent in sortedEvents)
|
||||||
|
{
|
||||||
|
switch (historyEvent.EventType)
|
||||||
|
{
|
||||||
|
// Episode got grabbed from a specific indexer. Attribute anything that happens to that indexer.
|
||||||
|
case History.HistoryEventType.Grabbed:
|
||||||
|
grabIndexer = historyEvent.Data.GetValueOrDefault("indexer") ?? string.Empty;
|
||||||
|
Apply(stats, grabIndexer, historyEvent.Date, s => s.Grabs++);
|
||||||
|
lastEvent = HistoryEventType.Grabbed;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Episodes got imported, only attribute the import if we grabbed it from an indexer.
|
||||||
|
// Try attribute the deletion/replacement to the previous indexer.
|
||||||
|
case History.HistoryEventType.SeriesFolderImported:
|
||||||
|
case History.HistoryEventType.DownloadFolderImported:
|
||||||
|
if (lastEvent == HistoryEventType.Grabbed)
|
||||||
|
{
|
||||||
|
if (importIndexer != null)
|
||||||
|
{
|
||||||
|
Apply(stats, importIndexer, historyEvent.Date, s => s.Replaced++);
|
||||||
|
}
|
||||||
|
importIndexer = grabIndexer;
|
||||||
|
grabIndexer = null;
|
||||||
|
Apply(stats, importIndexer, historyEvent.Date, s => s.Imported++);
|
||||||
|
lastEvent = HistoryEventType.DownloadFolderImported;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lastEvent = HistoryEventType.Unknown;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Attribute the failure to the indexer if we haven't imported yet.
|
||||||
|
case History.HistoryEventType.DownloadFailed:
|
||||||
|
if (lastEvent == HistoryEventType.Grabbed)
|
||||||
|
{
|
||||||
|
Apply(stats, grabIndexer, historyEvent.Date, s => s.Failed++);
|
||||||
|
grabIndexer = null;
|
||||||
|
lastEvent = HistoryEventType.Unknown;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case History.HistoryEventType.EpisodeFileDeleted:
|
||||||
|
lastEvent = HistoryEventType.Unknown;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case History.HistoryEventType.Unknown:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Apply<T>(Dictionary<string, StatisticsGrouping<T>> stats, string key, DateTime date, Action<T> applyAction) where T : new()
|
||||||
|
{
|
||||||
|
StatisticsGrouping<T> group;
|
||||||
|
|
||||||
|
if (!stats.TryGetValue(key, out group))
|
||||||
|
{
|
||||||
|
stats[key] = group = new StatisticsGrouping<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Apply(date, applyAction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue