More Stat collection (#1337)

* Ensure that Scan Series triggers a file analysis task.

* Tweaked concurrency for Analyze Files

* Implemented new stats tracking for upcoming performance release.
This commit is contained in:
Joseph Milazzo 2022-06-27 10:23:32 -05:00 committed by GitHub
parent 77cd1bc80a
commit c809597cf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 9 deletions

View File

@ -2,49 +2,122 @@
namespace API.DTOs.Stats
{
/// <summary>
/// Represents information about a Kavita Installation
/// </summary>
public class ServerInfoDto
{
/// <summary>
/// Unique Id that represents a unique install
/// </summary>
public string InstallId { get; set; }
public string Os { get; set; }
/// <summary>
/// If the Kavita install is using Docker
/// </summary>
public bool IsDocker { get; set; }
/// <summary>
/// Version of .NET instance is running
/// </summary>
public string DotnetVersion { get; set; }
/// <summary>
/// Version of Kavita
/// </summary>
public string KavitaVersion { get; set; }
/// <summary>
/// Number of Cores on the instance
/// </summary>
public int NumOfCores { get; set; }
/// <summary>
/// The number of libraries on the instance
/// </summary>
public int NumberOfLibraries { get; set; }
/// <summary>
/// Does any user have bookmarks
/// </summary>
public bool HasBookmarks { get; set; }
/// <summary>
/// The site theme the install is using
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public string ActiveSiteTheme { get; set; }
/// <summary>
/// The reading mode the main user has as a preference
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public ReaderMode MangaReaderMode { get; set; }
/// <summary>
/// Number of users on the install
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public int NumberOfUsers { get; set; }
/// <summary>
/// Number of collections on the install
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public int NumberOfCollections { get; set; }
/// <summary>
/// Number of reading lists on the install (Sum of all users)
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public int NumberOfReadingLists { get; set; }
/// <summary>
/// Is OPDS enabled
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public bool OPDSEnabled { get; set; }
/// <summary>
/// Total number of files in the instance
/// </summary>
/// <remarks>Introduced in v0.5.2</remarks>
public int TotalFiles { get; set; }
/// <summary>
/// Total number of Genres in the instance
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int TotalGenres { get; set; }
/// <summary>
/// Total number of People in the instance
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int TotalPeople { get; set; }
/// <summary>
/// Is this instance storing bookmarks as WebP
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public bool StoreBookmarksAsWebP { get; set; }
/// <summary>
/// Number of users on this instance using Card Layout
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int UsersOnCardLayout { get; set; }
/// <summary>
/// Number of users on this instance using List Layout
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int UsersOnListLayout { get; set; }
/// <summary>
/// Max number of Series for any library on the instance
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int MaxSeriesInALibrary { get; set; }
/// <summary>
/// Max number of Volumes for any library on the instance
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int MaxVolumesInASeries { get; set; }
/// <summary>
/// Max number of Chapters for any library on the instance
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public int MaxChaptersInASeries { get; set; }
/// <summary>
/// Does this instance have relationships setup between series
/// </summary>
/// <remarks>Introduced in v0.5.4</remarks>
public bool UsingSeriesRelationships { get; set; }
}
}

View File

@ -18,6 +18,7 @@ public interface IGenreRepository
Task<IList<GenreTagDto>> GetAllGenreDtosAsync();
Task RemoveAllGenreNoLongerAssociated(bool removeExternal = false);
Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(IList<int> libraryIds);
Task<int> GetCountAsync();
}
public class GenreRepository : IGenreRepository
@ -72,6 +73,11 @@ public class GenreRepository : IGenreRepository
.ToListAsync();
}
public async Task<int> GetCountAsync()
{
return await _context.Genre.CountAsync();
}
public async Task<IList<Genre>> GetAllGenresAsync()
{
return await _context.Genre.ToListAsync();

View File

@ -16,6 +16,7 @@ public interface IPersonRepository
Task<IList<Person>> GetAllPeople();
Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false);
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds);
Task<int> GetCountAsync();
}
public class PersonRepository : IPersonRepository
@ -72,6 +73,11 @@ public class PersonRepository : IPersonRepository
.ToListAsync();
}
public async Task<int> GetCountAsync()
{
return await _context.Person.CountAsync();
}
public async Task<IList<Person>> GetAllPeople()
{

View File

@ -57,6 +57,7 @@ public interface IUserRepository
Task<IEnumerable<AppUserPreferences>> GetAllPreferencesByThemeAsync(int themeId);
Task<bool> HasAccessToLibrary(int libraryId, int userId);
Task<IEnumerable<AppUser>> GetAllUsersAsync(AppUserIncludes includeFlags);
}
public class UserRepository : IUserRepository
@ -246,6 +247,12 @@ public class UserRepository : IUserRepository
.AnyAsync(library => library.AppUsers.Any(user => user.Id == userId));
}
public async Task<IEnumerable<AppUser>> GetAllUsersAsync(AppUserIncludes includeFlags)
{
var query = AddIncludesToQuery(_context.Users.AsQueryable(), includeFlags);
return await query.ToListAsync();
}
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
{
return await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole);

View File

@ -18,7 +18,7 @@ namespace API.Services.Tasks.Metadata;
public interface IWordCountAnalyzerService
{
[DisableConcurrentExecution(timeoutInSeconds: 60 * 60 * 60)]
[AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
[AutomaticRetry(Attempts = 2, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
Task ScanLibrary(int libraryId, bool forceUpdate = false);
Task ScanSeries(int libraryId, int seriesId, bool forceUpdate = true);
}
@ -46,7 +46,7 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
[DisableConcurrentExecution(timeoutInSeconds: 60 * 60 * 60)]
[AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
[AutomaticRetry(Attempts = 2, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public async Task ScanLibrary(int libraryId, bool forceUpdate = false)
{
var sw = Stopwatch.StartNew();

View File

@ -191,6 +191,7 @@ public class ScannerService : IScannerService
await CleanupDbEntities();
BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds));
BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, series.Id, false));
BackgroundJob.Enqueue(() => _wordCountAnalyzerService.ScanSeries(libraryId, series.Id, false));
}
private static void RemoveParsedInfosNotForSeries(Dictionary<ParsedSeries, List<ParserInfo>> parsedSeries, Series series)

View File

@ -4,13 +4,15 @@ using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs.Stats;
using API.DTOs.Theme;
using API.Entities.Enums;
using API.Entities.Enums.UserPreferences;
using Flurl.Http;
using Kavita.Common.EnvironmentInfo;
using Kavita.Common.Helpers;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Services.Tasks;
@ -24,12 +26,14 @@ public class StatsService : IStatsService
{
private readonly ILogger<StatsService> _logger;
private readonly IUnitOfWork _unitOfWork;
private readonly DataContext _context;
private const string ApiUrl = "https://stats.kavitareader.com";
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork)
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork, DataContext context)
{
_logger = logger;
_unitOfWork = unitOfWork;
_context = context;
FlurlHttp.ConfigureClient(ApiUrl, cli =>
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
@ -102,6 +106,8 @@ public class StatsService : IStatsService
var installId = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallId);
var installVersion = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);
var serverSettings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
var serverInfo = new ServerInfoDto
{
InstallId = installId.Value,
@ -114,11 +120,24 @@ public class StatsService : IStatsService
NumberOfLibraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).Count(),
NumberOfCollections = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).Count(),
NumberOfReadingLists = await _unitOfWork.ReadingListRepository.Count(),
OPDSEnabled = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds,
OPDSEnabled = serverSettings.EnableOpds,
NumberOfUsers = (await _unitOfWork.UserRepository.GetAllUsers()).Count(),
TotalFiles = await _unitOfWork.LibraryRepository.GetTotalFiles(),
TotalGenres = await _unitOfWork.GenreRepository.GetCountAsync(),
TotalPeople = await _unitOfWork.PersonRepository.GetCountAsync(),
UsingSeriesRelationships = await GetIfUsingSeriesRelationship(),
StoreBookmarksAsWebP = serverSettings.ConvertBookmarkToWebP,
MaxSeriesInALibrary = await MaxSeriesInAnyLibrary(),
MaxVolumesInASeries = await MaxVolumesInASeries(),
MaxChaptersInASeries = await MaxChaptersInASeries(),
};
var usersWithPref = (await _unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.UserPreferences)).ToList();
serverInfo.UsersOnCardLayout =
usersWithPref.Count(u => u.UserPreferences.GlobalPageLayoutMode == PageLayoutMode.Cards);
serverInfo.UsersOnListLayout =
usersWithPref.Count(u => u.UserPreferences.GlobalPageLayoutMode == PageLayoutMode.List);
var firstAdminUser = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).FirstOrDefault();
if (firstAdminUser != null)
@ -132,4 +151,40 @@ public class StatsService : IStatsService
return serverInfo;
}
private Task<bool> GetIfUsingSeriesRelationship()
{
return _context.SeriesRelation.AnyAsync();
}
private Task<int> MaxSeriesInAnyLibrary()
{
return _context.Series
.Select(s => new
{
LibraryId = s.LibraryId,
Count = _context.Library.Where(l => l.Id == s.LibraryId).SelectMany(l => l.Series).Count()
})
.MaxAsync(d => d.Count);
}
private Task<int> MaxVolumesInASeries()
{
return _context.Volume
.Select(v => new
{
v.SeriesId,
Count = _context.Series.Where(s => s.Id == v.SeriesId).SelectMany(s => s.Volumes).Count()
})
.MaxAsync(d => d.Count);
}
private Task<int> MaxChaptersInASeries()
{
return _context.Series
.MaxAsync(s => s.Volumes
.Where(v => v.Number == 0)
.SelectMany(v => v.Chapters)
.Count());
}
}