Browse Source

fix slow db and api logging so it also works in scanner project

pull/2747/head
Jason Dove 7 months ago
parent
commit
d1b994a6c0
No known key found for this signature in database
  1. 5
      ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs
  2. 21
      ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs
  3. 16
      ErsatzTV.Infrastructure/Data/TvContext.cs
  4. 3
      ErsatzTV.Infrastructure/SlowApiHandler.cs
  5. 10
      ErsatzTV.Infrastructure/SlowQueryInterceptor.cs
  6. 6
      ErsatzTV.Scanner/Program.cs
  7. 18
      ErsatzTV/Startup.cs

5
ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs

@ -88,6 +88,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -88,6 +88,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinShow> maybeExisting = await dbContext.JellyfinShows
.TagWithCallSite()
.Include(m => m.LibraryPath)
.ThenInclude(lp => lp.Library)
.Include(m => m.ShowMetadata)
@ -128,6 +129,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -128,6 +129,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinSeason> maybeExisting = await dbContext.JellyfinSeasons
.TagWithCallSite()
.Include(m => m.LibraryPath)
.Include(m => m.SeasonMetadata)
.ThenInclude(mm => mm.Artwork)
@ -158,6 +160,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -158,6 +160,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinEpisode> maybeExisting = await dbContext.JellyfinEpisodes
.TagWithCallSite()
.Include(m => m.LibraryPath)
.ThenInclude(lp => lp.Library)
.Include(m => m.MediaVersions)
@ -511,6 +514,8 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository @@ -511,6 +514,8 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<JellyfinShow> maybeShow = await dbContext.JellyfinShows
.AsNoTracking()
.TagWithCallSite()
.Where(s => s.Id == showId)
.Where(s => s.LibraryPath.LibraryId == libraryId)
.Include(s => s.ShowMetadata)

21
ErsatzTV.Infrastructure/Data/Repositories/SearchRepository.cs

@ -15,6 +15,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -15,6 +15,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
var baseItem = await dbContext.MediaItems
.AsNoTracking()
.TagWithCallSite()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
if (baseItem is null)
@ -27,60 +28,70 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -27,60 +28,70 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
case Movie:
return await dbContext.Movies
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case Episode:
return await dbContext.Episodes
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case Season:
return await dbContext.Seasons
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case Show:
return await dbContext.Shows
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case MusicVideo:
return await dbContext.MusicVideos
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case Artist:
return await dbContext.Artists
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case OtherVideo:
return await dbContext.OtherVideos
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case Song:
return await dbContext.Songs
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case Image:
return await dbContext.Images
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
case RemoteStream:
return await dbContext.RemoteStreams
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.SingleOrDefaultAsync(mi => mi.Id == id, cancellationToken);
@ -225,6 +236,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -225,6 +236,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Movie> movies = dbContext.Movies
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -242,6 +254,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -242,6 +254,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Show> shows = dbContext.Shows
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -259,6 +272,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -259,6 +272,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Season> seasons = dbContext.Seasons
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -276,6 +290,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -276,6 +290,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Episode> episodes = dbContext.Episodes
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -294,6 +309,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -294,6 +309,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<MusicVideo> musicVideos = dbContext.MusicVideos
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -311,6 +327,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -311,6 +327,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Artist> artists = dbContext.Artists
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -329,6 +346,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -329,6 +346,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<OtherVideo> otherVideos = dbContext.OtherVideos
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -346,6 +364,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -346,6 +364,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Song> songs = dbContext.Songs
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -363,6 +382,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -363,6 +382,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<Image> images = dbContext.Images
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()
@ -381,6 +401,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I @@ -381,6 +401,7 @@ public class SearchRepository(IDbContextFactory<TvContext> dbContextFactory) : I
ConfiguredCancelableAsyncEnumerable<RemoteStream> remoteStreams = dbContext.RemoteStreams
.AsNoTracking()
.TagWithCallSite()
.IncludeForSearch()
.AsSplitQuery()
.AsAsyncEnumerable()

16
ErsatzTV.Infrastructure/Data/TvContext.cs

@ -10,10 +10,17 @@ namespace ErsatzTV.Infrastructure.Data; @@ -10,10 +10,17 @@ namespace ErsatzTV.Infrastructure.Data;
public class TvContext : DbContext
{
private readonly ILoggerFactory _loggerFactory;
private readonly SlowQueryInterceptor _slowQueryInterceptor;
public TvContext(DbContextOptions<TvContext> options, ILoggerFactory loggerFactory)
: base(options) =>
public TvContext(
DbContextOptions<TvContext> options,
ILoggerFactory loggerFactory,
SlowQueryInterceptor slowQueryInterceptor)
: base(options)
{
_loggerFactory = loggerFactory;
_slowQueryInterceptor = slowQueryInterceptor;
}
public static string LastInsertedRowId { get; set; } = "last_insert_rowid()";
public static string CaseInsensitiveCollation { get; set; } = "NOCASE";
@ -119,8 +126,11 @@ public class TvContext : DbContext @@ -119,8 +126,11 @@ public class TvContext : DbContext
public DbSet<Subtitle> Subtitles { get; set; }
public DbSet<GraphicsElement> GraphicsElements { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(_loggerFactory);
optionsBuilder.AddInterceptors(_slowQueryInterceptor);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{

3
ErsatzTV/SlowApiHandler.cs → ErsatzTV.Infrastructure/SlowApiHandler.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using ErsatzTV.Core;
using Microsoft.Extensions.Logging;
namespace ErsatzTV;
namespace ErsatzTV.Infrastructure;
using System.Diagnostics;

10
ErsatzTV/SlowQueryInterceptor.cs → ErsatzTV.Infrastructure/SlowQueryInterceptor.cs

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
using System.Data.Common;
using ErsatzTV.Core;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace ErsatzTV;
namespace ErsatzTV.Infrastructure;
public class SlowQueryInterceptor(int threshold) : DbCommandInterceptor
public class SlowQueryInterceptor(ILogger<SlowQueryInterceptor> logger) : DbCommandInterceptor
{
public override ValueTask<DbDataReader> ReaderExecutedAsync(
DbCommand command,
@ -11,9 +13,9 @@ public class SlowQueryInterceptor(int threshold) : DbCommandInterceptor @@ -11,9 +13,9 @@ public class SlowQueryInterceptor(int threshold) : DbCommandInterceptor
DbDataReader result,
CancellationToken cancellationToken = default)
{
if (eventData.Duration.TotalMilliseconds > threshold)
if (SystemEnvironment.SlowDbMs > 0 && eventData.Duration.TotalMilliseconds > SystemEnvironment.SlowDbMs)
{
Serilog.Log.Logger.Debug(
logger.LogDebug(
"[SLOW QUERY] ({Milliseconds}ms): {Command}",
eventData.Duration.TotalMilliseconds,
command.CommandText);

6
ErsatzTV.Scanner/Program.cs

@ -19,6 +19,7 @@ using ErsatzTV.Core.Plex; @@ -19,6 +19,7 @@ using ErsatzTV.Core.Plex;
using ErsatzTV.Core.Search;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data.Repositories;
using ErsatzTV.Infrastructure.Emby;
@ -171,6 +172,8 @@ public class Program @@ -171,6 +172,8 @@ public class Program
services.AddHttpClient();
services.AddHttpClient("RefitCustomClient").AddHttpMessageHandler<SlowApiHandler>();
services.AddScoped<IConfigElementRepository, ConfigElementRepository>();
services.AddScoped<IMetadataRepository, MetadataRepository>();
services.AddScoped<IMediaSourceRepository, MediaSourceRepository>();
@ -256,6 +259,9 @@ public class Program @@ -256,6 +259,9 @@ public class Program
services.AddSingleton<IFileSystem, RealFileSystem>();
services.AddTransient<SlowApiHandler>();
services.AddTransient<SlowQueryInterceptor>();
services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining<Worker>());
services.AddMemoryCache();

18
ErsatzTV/Startup.cs

@ -51,6 +51,7 @@ using ErsatzTV.FFmpeg.Pipeline; @@ -51,6 +51,7 @@ using ErsatzTV.FFmpeg.Pipeline;
using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Filters;
using ErsatzTV.Formatters;
using ErsatzTV.Infrastructure;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data.Repositories;
using ErsatzTV.Infrastructure.Database;
@ -408,12 +409,6 @@ public class Startup @@ -408,12 +409,6 @@ public class Startup
var sqliteConnectionString = $"Data Source={FileSystemLayout.DatabasePath};foreign keys=true;";
string mySqlConnectionString = Configuration.GetValue<string>("MySql:ConnectionString");
SlowQueryInterceptor interceptor = null;
if (SystemEnvironment.SlowDbMs.HasValue)
{
interceptor = new SlowQueryInterceptor(SystemEnvironment.SlowDbMs.Value);
}
services.AddDbContext<TvContext>(
options =>
{
@ -444,11 +439,6 @@ public class Startup @@ -444,11 +439,6 @@ public class Startup
}
);
}
if (interceptor != null)
{
options.AddInterceptors(interceptor);
}
},
ServiceLifetime.Scoped,
ServiceLifetime.Singleton);
@ -478,11 +468,6 @@ public class Startup @@ -478,11 +468,6 @@ public class Startup
}
);
}
if (interceptor != null)
{
options.AddInterceptors(interceptor);
}
});
if (databaseProvider == Provider.Sqlite.Name)
@ -870,6 +855,7 @@ public class Startup @@ -870,6 +855,7 @@ public class Startup
// services.AddTransient(typeof(IRequestHandler<,>), typeof(GetRecentLogEntriesHandler<>));
services.AddTransient<SlowApiHandler>();
services.AddTransient<SlowQueryInterceptor>();
// run-once/blocking startup services
services.AddHostedService<EndpointValidatorService>();

Loading…
Cancel
Save