From 74959284f34140a6bb0cdb41f908ed3ed0dfafa1 Mon Sep 17 00:00:00 2001 From: Roman Efremov Date: Tue, 17 Dec 2024 09:35:28 +0500 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20Persistence.Benchmark?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Persistence.API/appsettings.json | 3 +- .../Database/BenchmarkDbContext.cs | 12 +++ .../Database/DbConnection.cs | 14 ++++ .../Database/Entities/ParameterData.cs | 22 +++++ .../Persistence.Benchmark.csproj | 23 ++++++ Persistence.Benchmark/Program.cs | 18 ++++ .../TestHttpClientFactory.cs | 18 ++++ .../Tests/BaseIntegrationTest.cs | 26 ++++++ Persistence.Benchmark/Tests/WitsDataTest.cs | 82 +++++++++++++++++++ Persistence.Benchmark/WebAppFactoryFixture.cs | 71 ++++++++++++++++ Persistence.sln | 8 +- 11 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 Persistence.Benchmark/Database/BenchmarkDbContext.cs create mode 100644 Persistence.Benchmark/Database/DbConnection.cs create mode 100644 Persistence.Benchmark/Database/Entities/ParameterData.cs create mode 100644 Persistence.Benchmark/Persistence.Benchmark.csproj create mode 100644 Persistence.Benchmark/Program.cs create mode 100644 Persistence.Benchmark/TestHttpClientFactory.cs create mode 100644 Persistence.Benchmark/Tests/BaseIntegrationTest.cs create mode 100644 Persistence.Benchmark/Tests/WitsDataTest.cs create mode 100644 Persistence.Benchmark/WebAppFactoryFixture.cs diff --git a/Persistence.API/appsettings.json b/Persistence.API/appsettings.json index d4248fb..881e753 100644 --- a/Persistence.API/appsettings.json +++ b/Persistence.API/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore.Database.Command": "Error" } }, "ConnectionStrings": { diff --git a/Persistence.Benchmark/Database/BenchmarkDbContext.cs b/Persistence.Benchmark/Database/BenchmarkDbContext.cs new file mode 100644 index 0000000..96c4460 --- /dev/null +++ b/Persistence.Benchmark/Database/BenchmarkDbContext.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; +using Persistence.Benchmark.Database.Entities; +using Persistence.Database; + +namespace Persistence.Benchmark.Database; +public class BenchmarkDbContext : PersistenceDbContext +{ + public new DbSet ParameterData => Set(); + public BenchmarkDbContext(DbContextOptions options) : base(options) + { + } +} diff --git a/Persistence.Benchmark/Database/DbConnection.cs b/Persistence.Benchmark/Database/DbConnection.cs new file mode 100644 index 0000000..21632f9 --- /dev/null +++ b/Persistence.Benchmark/Database/DbConnection.cs @@ -0,0 +1,14 @@ +namespace Persistence.Benchmark.Database; +public class DbConnection +{ + public string Host { get; set; } = null!; + + public int Port { get; set; } + + public string Username { get; set; } = null!; + + public string Password { get; set; } = null!; + + public string GetConnectionString() => + $"Host={Host};Database={Guid.NewGuid()};Port={Port};Username={Username};Password={Password};Persist Security Info=True;Include Error Detail=True"; +} diff --git a/Persistence.Benchmark/Database/Entities/ParameterData.cs b/Persistence.Benchmark/Database/Entities/ParameterData.cs new file mode 100644 index 0000000..5c2d103 --- /dev/null +++ b/Persistence.Benchmark/Database/Entities/ParameterData.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Persistence.Benchmark.Database.Entities; +public class ParameterData +{ + [Key] + public Guid Id { get; set; } + + [Required, Comment("Дискриминатор системы")] + public Guid DiscriminatorId { get; set; } + + [Comment("Id параметра")] + public int ParameterId { get; set; } + + [Column(TypeName = "varchar(256)"), Comment("Значение параметра в виде строки")] + public required string Value { get; set; } + + [Comment("Временная отметка")] + public DateTimeOffset Timestamp { get; set; } +} diff --git a/Persistence.Benchmark/Persistence.Benchmark.csproj b/Persistence.Benchmark/Persistence.Benchmark.csproj new file mode 100644 index 0000000..a240125 --- /dev/null +++ b/Persistence.Benchmark/Persistence.Benchmark.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + + diff --git a/Persistence.Benchmark/Program.cs b/Persistence.Benchmark/Program.cs new file mode 100644 index 0000000..6f9874b --- /dev/null +++ b/Persistence.Benchmark/Program.cs @@ -0,0 +1,18 @@ +using BenchmarkDotNet.Running; +using Persistence.Benchmark; +using Persistence.Benchmark.Tests; +using Persistence.API; +using System.Runtime.InteropServices; + +public class Program +{ + private static void Main(string[] args) + { + //var host = BenchmarkSwitcher.FromAssembly(typeof(Persistence.API.Program).Assembly); + //host.Run + + //System.Console.OutputEncoding = System.Text.Encoding.UTF8; + + BenchmarkRunner.Run(); + } +} \ No newline at end of file diff --git a/Persistence.Benchmark/TestHttpClientFactory.cs b/Persistence.Benchmark/TestHttpClientFactory.cs new file mode 100644 index 0000000..bc9a4da --- /dev/null +++ b/Persistence.Benchmark/TestHttpClientFactory.cs @@ -0,0 +1,18 @@ +namespace Persistence.Benchmark; + +/// +/// Фабрика HTTP клиентов для интеграционных тестов +/// +public class TestHttpClientFactory : IHttpClientFactory +{ + private readonly WebAppFactoryFixture factory; + + public TestHttpClientFactory(WebAppFactoryFixture factory) + { + this.factory = factory; + } + public HttpClient CreateClient(string name) + { + return factory.CreateClient(); + } +} diff --git a/Persistence.Benchmark/Tests/BaseIntegrationTest.cs b/Persistence.Benchmark/Tests/BaseIntegrationTest.cs new file mode 100644 index 0000000..d627b58 --- /dev/null +++ b/Persistence.Benchmark/Tests/BaseIntegrationTest.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using Persistence.Database; +using Persistence.Database.Model; +using Xunit; + +namespace Persistence.Benchmark; +public abstract class BaseIntegrationTest : IClassFixture, IDisposable +{ + protected readonly IServiceScope scope; + + protected readonly PersistenceDbContext dbContext; + + protected BaseIntegrationTest(WebAppFactoryFixture factory) + { + scope = factory.Services.CreateScope(); + + dbContext = scope.ServiceProvider.GetRequiredService(); + } + + public void Dispose() + { + scope.Dispose(); + dbContext.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/Persistence.Benchmark/Tests/WitsDataTest.cs b/Persistence.Benchmark/Tests/WitsDataTest.cs new file mode 100644 index 0000000..5c675c2 --- /dev/null +++ b/Persistence.Benchmark/Tests/WitsDataTest.cs @@ -0,0 +1,82 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using Microsoft.Extensions.DependencyInjection; +using Persistence.Client; +using Persistence.Client.Clients; +using Persistence.Client.Clients.Interfaces; +using Persistence.Models; + +namespace Persistence.Benchmark.Tests; + +[SimpleJob(RunStrategy.ColdStart, 1)] +public class WitsDataTest +{ + private readonly IWitsDataClient client; + public IEnumerable data; + public WitsDataTest() + { + var factory = new WebAppFactoryFixture(); + + var scope = factory.Services.CreateScope(); + var persistenceClientFactory = scope.ServiceProvider + .GetRequiredService(); + client = persistenceClientFactory.GetWitsDataClient(); + + //data = GenerateData(1_000_000); + } + + [Benchmark] + [IterationCount(1)] + public async Task WithCompositePk() + { + try + { + //var data = GenerateData(1_000_000); + var response = await client.AddRange(data, CancellationToken.None); + Console.WriteLine(response.ToString()); + //var discriminatorId = data.FirstOrDefault()!.DiscriminatorId; + //var date = DateTimeOffset.UtcNow.AddDays(-1); + //Console.WriteLine(date.ToString()); + + //var test = await client.GetPart(discriminatorId, date); + //Console.WriteLine(test.FirstOrDefault()?.DiscriminatorId.ToString()); + } + catch (Exception ex) { + Console.WriteLine(ex.Message); + } + } + + [GlobalSetup] + public void GenerateData() + { + int countToCreate = 5_000_000; + + var dtos = new List(); + + for (var j = 0; j < countToCreate / 100 ; j++) + { + for (var i = 0; i < countToCreate && i < 100; i++) + { + var discriminatorId = Guid.NewGuid(); + var timestamped = DateTimeOffset.UtcNow; + var random = new Random(); + dtos.Add(new WitsDataDto() + { + DiscriminatorId = discriminatorId, + Timestamped = timestamped.AddSeconds(i), + Values = new List() + { + new WitsValueDto() + { + RecordId = i + 1, + ItemId = i + 1, + Value = random.Next(1, 100) + } + } + }); + } + } + + data = dtos; + } +} diff --git a/Persistence.Benchmark/WebAppFactoryFixture.cs b/Persistence.Benchmark/WebAppFactoryFixture.cs new file mode 100644 index 0000000..0096437 --- /dev/null +++ b/Persistence.Benchmark/WebAppFactoryFixture.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Persistence.API; +using Persistence.Benchmark.Database; +using Persistence.Client; +using Persistence.Database.Model; +using Persistence.Database.Postgres; + +namespace Persistence.Benchmark; +public class WebAppFactoryFixture : WebApplicationFactory +{ + private string connectionString = string.Empty; + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureAppConfiguration((hostingContext, config) => + { + config.AddJsonFile("appsettings.Tests.json"); + + var dbConnection = config.Build().GetSection("DbConnection").Get()!; + connectionString = dbConnection.GetConnectionString(); + }); + + builder.ConfigureServices(services => + { + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); + if (descriptor != null) + services.Remove(descriptor); + + services.AddDbContext(options => + options.UseNpgsql(connectionString)); + + services.AddLogging(builder => builder.AddConsole()); + + services.RemoveAll(); + services.AddSingleton(provider => + { + return new TestHttpClientFactory(this); + }); + + services.AddSingleton(); + + var serviceProvider = services.BuildServiceProvider(); + + using var scope = serviceProvider.CreateScope(); + var scopedServices = scope.ServiceProvider; + + var dbContext = scopedServices.GetRequiredService(); + //dbContext.Database.SetCommandTimeout(5); + dbContext.Database.EnsureCreatedAndMigrated(); + dbContext.SaveChanges(); + }); + } + + public override async ValueTask DisposeAsync() + { + var dbContext = new PersistencePostgresContext( + new DbContextOptionsBuilder() + .UseNpgsql(connectionString) + .Options); + + await dbContext.Database.EnsureDeletedAsync(); + + GC.SuppressFinalize(this); + } +} diff --git a/Persistence.sln b/Persistence.sln index a8115a8..c3c8eef 100644 --- a/Persistence.sln +++ b/Persistence.sln @@ -15,7 +15,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.IntegrationTest EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Database.Postgres", "Persistence.Database.Postgres\Persistence.Database.Postgres.csproj", "{CC284D27-162D-490C-B6CF-74D666B7C5F3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.Client", "Persistence.Client\Persistence.Client.csproj", "{84B68660-48E6-4974-A4E5-517552D9DE23}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Client", "Persistence.Client\Persistence.Client.csproj", "{84B68660-48E6-4974-A4E5-517552D9DE23}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.Benchmark", "Persistence.Benchmark\Persistence.Benchmark.csproj", "{7D358CCB-41E1-4C78-A33A-F21DE929F3B4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -51,6 +53,10 @@ Global {84B68660-48E6-4974-A4E5-517552D9DE23}.Debug|Any CPU.Build.0 = Debug|Any CPU {84B68660-48E6-4974-A4E5-517552D9DE23}.Release|Any CPU.ActiveCfg = Release|Any CPU {84B68660-48E6-4974-A4E5-517552D9DE23}.Release|Any CPU.Build.0 = Release|Any CPU + {7D358CCB-41E1-4C78-A33A-F21DE929F3B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D358CCB-41E1-4C78-A33A-F21DE929F3B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D358CCB-41E1-4C78-A33A-F21DE929F3B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D358CCB-41E1-4C78-A33A-F21DE929F3B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE