mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
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:
parent
77cd1bc80a
commit
c809597cf0
@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user