Compare commits

...

1 Commits

Author SHA1 Message Date
Taloth Saldono a19776553e WIP: Statistics API endpoint. 2016-01-31 15:33:13 +01:00
6 changed files with 260 additions and 2 deletions

View File

@ -230,6 +230,7 @@
<Compile Include="Series\SeasonStatisticsResource.cs" />
<Compile Include="System\Backup\BackupModule.cs" />
<Compile Include="System\Backup\BackupResource.cs" />
<Compile Include="System\Statistics\StatisticsModule.cs" />
<Compile Include="System\Tasks\TaskModule.cs" />
<Compile Include="System\Tasks\TaskResource.cs" />
<Compile Include="System\SystemModule.cs" />

View File

@ -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;
}
}
}
}

View File

@ -954,6 +954,9 @@
<Compile Include="SeriesStats\SeriesStatistics.cs" />
<Compile Include="SeriesStats\SeriesStatisticsRepository.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\TagRepository.cs" />
<Compile Include="Tags\TagService.cs" />

View File

@ -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; }
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}