mirror of https://github.com/Squidex/squidex.git
25 changed files with 1186 additions and 88 deletions
@ -0,0 +1,38 @@ |
|||
// ==========================================================================
|
|||
// BsonHelper.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Infrastructure.MongoDb |
|||
{ |
|||
public static class BsonHelper |
|||
{ |
|||
public static string UnescapeBson(this string value) |
|||
{ |
|||
return ReplaceFirstCharacter(value, '§', '$'); |
|||
} |
|||
|
|||
public static string EscapeJson(this string value) |
|||
{ |
|||
return ReplaceFirstCharacter(value, '$', '§'); |
|||
} |
|||
|
|||
private static string ReplaceFirstCharacter(string value, char toReplace, char replacement) |
|||
{ |
|||
if (value.Length == 0 || value[0] != toReplace) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
if (value.Length == 1) |
|||
{ |
|||
return toReplace.ToString(); |
|||
} |
|||
|
|||
return replacement + value.Substring(1); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
// ==========================================================================
|
|||
// BsonJsonReader.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.IO; |
|||
using NewtonsoftJsonReader = Newtonsoft.Json.JsonReader; |
|||
using NewtonsoftJsonToken = Newtonsoft.Json.JsonToken; |
|||
|
|||
namespace Squidex.Infrastructure.MongoDb |
|||
{ |
|||
public sealed class BsonJsonReader : NewtonsoftJsonReader |
|||
{ |
|||
private readonly IBsonReader bsonReader; |
|||
|
|||
public BsonJsonReader(IBsonReader bsonReader) |
|||
{ |
|||
Guard.NotNull(bsonReader, nameof(bsonReader)); |
|||
|
|||
this.bsonReader = bsonReader; |
|||
} |
|||
|
|||
public override bool Read() |
|||
{ |
|||
if (bsonReader.State == BsonReaderState.Type) |
|||
{ |
|||
bsonReader.ReadBsonType(); |
|||
} |
|||
|
|||
if (bsonReader.State == BsonReaderState.Name) |
|||
{ |
|||
SetToken(NewtonsoftJsonToken.PropertyName, bsonReader.ReadName().UnescapeBson()); |
|||
} |
|||
else if (bsonReader.State == BsonReaderState.EndOfDocument) |
|||
{ |
|||
SetToken(NewtonsoftJsonToken.EndObject); |
|||
bsonReader.ReadEndDocument(); |
|||
} |
|||
else if (bsonReader.State == BsonReaderState.EndOfArray) |
|||
{ |
|||
SetToken(NewtonsoftJsonToken.EndArray); |
|||
bsonReader.ReadEndArray(); |
|||
} |
|||
else if (bsonReader.State == BsonReaderState.Value) |
|||
{ |
|||
switch (bsonReader.CurrentBsonType) |
|||
{ |
|||
case BsonType.Document: |
|||
SetToken(NewtonsoftJsonToken.StartObject); |
|||
bsonReader.ReadStartDocument(); |
|||
break; |
|||
case BsonType.Array: |
|||
SetToken(NewtonsoftJsonToken.StartArray); |
|||
bsonReader.ReadStartArray(); |
|||
break; |
|||
case BsonType.Undefined: |
|||
SetToken(NewtonsoftJsonToken.Undefined); |
|||
break; |
|||
case BsonType.Null: |
|||
SetToken(NewtonsoftJsonToken.Null); |
|||
break; |
|||
case BsonType.String: |
|||
SetToken(NewtonsoftJsonToken.String, bsonReader.ReadString()); |
|||
break; |
|||
case BsonType.Binary: |
|||
SetToken(NewtonsoftJsonToken.Bytes, bsonReader.ReadBinaryData().Bytes); |
|||
break; |
|||
case BsonType.Boolean: |
|||
SetToken(NewtonsoftJsonToken.Boolean, bsonReader.ReadBoolean()); |
|||
break; |
|||
case BsonType.DateTime: |
|||
SetToken(NewtonsoftJsonToken.Date, bsonReader.ReadDateTime()); |
|||
break; |
|||
case BsonType.Int32: |
|||
SetToken(NewtonsoftJsonToken.Integer, bsonReader.ReadInt32()); |
|||
break; |
|||
case BsonType.Int64: |
|||
SetToken(NewtonsoftJsonToken.Integer, bsonReader.ReadInt64()); |
|||
break; |
|||
case BsonType.Double: |
|||
SetToken(NewtonsoftJsonToken.Float, bsonReader.ReadDouble()); |
|||
break; |
|||
case BsonType.Decimal128: |
|||
SetToken(NewtonsoftJsonToken.Float, Decimal128.ToDouble(bsonReader.ReadDecimal128())); |
|||
break; |
|||
default: |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
|
|||
return !bsonReader.IsAtEndOfFile(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,166 @@ |
|||
// ==========================================================================
|
|||
// BsonJsonWriter.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using MongoDB.Bson.IO; |
|||
using NewtonsoftJSonWriter = Newtonsoft.Json.JsonWriter; |
|||
|
|||
namespace Squidex.Infrastructure.MongoDb |
|||
{ |
|||
public sealed class BsonJsonWriter : NewtonsoftJSonWriter |
|||
{ |
|||
private readonly IBsonWriter bsonWriter; |
|||
|
|||
public BsonJsonWriter(IBsonWriter bsonWriter) |
|||
{ |
|||
Guard.NotNull(bsonWriter, nameof(bsonWriter)); |
|||
|
|||
this.bsonWriter = bsonWriter; |
|||
} |
|||
|
|||
public override void WritePropertyName(string name, bool escape) |
|||
{ |
|||
bsonWriter.WriteName(name.EscapeJson()); |
|||
} |
|||
|
|||
public override void WriteStartArray() |
|||
{ |
|||
bsonWriter.WriteStartArray(); |
|||
} |
|||
|
|||
public override void WriteEndArray() |
|||
{ |
|||
bsonWriter.WriteEndArray(); |
|||
} |
|||
|
|||
public override void WriteStartObject() |
|||
{ |
|||
bsonWriter.WriteStartDocument(); |
|||
} |
|||
|
|||
public override void WriteEndObject() |
|||
{ |
|||
bsonWriter.WriteEndDocument(); |
|||
} |
|||
|
|||
public override void WriteNull() |
|||
{ |
|||
bsonWriter.WriteNull(); |
|||
} |
|||
|
|||
public override void WriteUndefined() |
|||
{ |
|||
bsonWriter.WriteUndefined(); |
|||
} |
|||
|
|||
public override void WriteValue(string value) |
|||
{ |
|||
bsonWriter.WriteString(value); |
|||
} |
|||
|
|||
public override void WriteValue(int value) |
|||
{ |
|||
bsonWriter.WriteInt32(value); |
|||
} |
|||
|
|||
public override void WriteValue(uint value) |
|||
{ |
|||
bsonWriter.WriteInt32((int)value); |
|||
} |
|||
|
|||
public override void WriteValue(long value) |
|||
{ |
|||
bsonWriter.WriteInt64(value); |
|||
} |
|||
|
|||
public override void WriteValue(ulong value) |
|||
{ |
|||
bsonWriter.WriteInt64((long)value); |
|||
} |
|||
|
|||
public override void WriteValue(float value) |
|||
{ |
|||
bsonWriter.WriteDouble(value); |
|||
} |
|||
|
|||
public override void WriteValue(double value) |
|||
{ |
|||
bsonWriter.WriteDouble(value); |
|||
} |
|||
|
|||
public override void WriteValue(bool value) |
|||
{ |
|||
bsonWriter.WriteBoolean(value); |
|||
} |
|||
|
|||
public override void WriteValue(short value) |
|||
{ |
|||
bsonWriter.WriteInt32(value); |
|||
} |
|||
|
|||
public override void WriteValue(ushort value) |
|||
{ |
|||
bsonWriter.WriteInt32(value); |
|||
} |
|||
|
|||
public override void WriteValue(char value) |
|||
{ |
|||
bsonWriter.WriteInt32(value); |
|||
} |
|||
|
|||
public override void WriteValue(byte value) |
|||
{ |
|||
bsonWriter.WriteInt32(value); |
|||
} |
|||
|
|||
public override void WriteValue(sbyte value) |
|||
{ |
|||
bsonWriter.WriteInt32(value); |
|||
} |
|||
|
|||
public override void WriteValue(decimal value) |
|||
{ |
|||
bsonWriter.WriteDecimal128(value); |
|||
} |
|||
|
|||
public override void WriteValue(DateTime value) |
|||
{ |
|||
bsonWriter.WriteString(value.ToString()); |
|||
} |
|||
|
|||
public override void WriteValue(DateTimeOffset value) |
|||
{ |
|||
bsonWriter.WriteString(value.ToString()); |
|||
} |
|||
|
|||
public override void WriteValue(byte[] value) |
|||
{ |
|||
bsonWriter.WriteBytes(value); |
|||
} |
|||
|
|||
public override void WriteValue(TimeSpan value) |
|||
{ |
|||
bsonWriter.WriteString(value.ToString()); |
|||
} |
|||
|
|||
public override void WriteValue(Guid value) |
|||
{ |
|||
bsonWriter.WriteString(value.ToString()); |
|||
} |
|||
|
|||
public override void WriteValue(Uri value) |
|||
{ |
|||
bsonWriter.WriteString(value.ToString()); |
|||
} |
|||
|
|||
public override void Flush() |
|||
{ |
|||
bsonWriter.Flush(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// ==========================================================================
|
|||
// MongoState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Infrastructure.States |
|||
{ |
|||
public sealed class MongoState<T> |
|||
{ |
|||
[BsonId] |
|||
[BsonElement] |
|||
[BsonRepresentation(BsonType.String)] |
|||
public string Id { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
public string Etag { get; set; } |
|||
|
|||
[BsonRequired] |
|||
[BsonElement] |
|||
[BsonJson] |
|||
public T Doc { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>netcoreapp2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Squidex.Domain.Apps.Read\Squidex.Domain.Apps.Read.csproj" /> |
|||
<ProjectReference Include="..\..\src\Squidex.Infrastructure.MongoDb\Squidex.Infrastructure.MongoDb.csproj" /> |
|||
<ProjectReference Include="..\..\src\Squidex.Infrastructure\Squidex.Infrastructure.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<PackageReference Include="MongoDB.Driver" Version="2.4.4" /> |
|||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="2.0.0" /> |
|||
<PackageReference Include="RefactoringEssentials" Version="5.4.0" /> |
|||
<PackageReference Include="StyleCop.Analyzers" Version="1.0.2" /> |
|||
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<Reference Include="Microsoft.Extensions.DependencyInjection"> |
|||
<HintPath>C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.dependencyinjection\2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll</HintPath> |
|||
</Reference> |
|||
</ItemGroup> |
|||
<PropertyGroup> |
|||
<CodeAnalysisRuleSet>..\..\Squidex.ruleset</CodeAnalysisRuleSet> |
|||
</PropertyGroup> |
|||
</Project> |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// IBenchmark.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Benchmarks |
|||
{ |
|||
public interface IBenchmark |
|||
{ |
|||
void RunInitialize(); |
|||
|
|||
long Run(); |
|||
|
|||
void RunCleanup(); |
|||
} |
|||
} |
|||
@ -0,0 +1,90 @@ |
|||
// ==========================================================================
|
|||
// Program.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using Benchmarks.Tests; |
|||
|
|||
namespace Benchmarks |
|||
{ |
|||
public static class Program |
|||
{ |
|||
private static readonly List<(string Name, IBenchmark Benchmark)> Benchmarks = new IBenchmark[] |
|||
{ |
|||
new AppendToEventStore(), |
|||
new AppendToEventStoreWithManyWriters(), |
|||
new HandleEvents(), |
|||
new HandleEventsWithManyWriters(), |
|||
new ReadSchemaState() |
|||
}.Select(x => (x.GetType().Name, x)).ToList(); |
|||
|
|||
public static void Main(string[] args) |
|||
{ |
|||
var name = "ReadSchemaState"; |
|||
|
|||
var selected = Benchmarks.Find(x => x.Name == name); |
|||
|
|||
if (selected.Benchmark == null) |
|||
{ |
|||
Console.WriteLine($"'{name}' is not a valid benchmark, please try: "); |
|||
|
|||
foreach (var b in Benchmarks) |
|||
{ |
|||
Console.WriteLine($" * {b.Name}"); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
const int numRuns = 3; |
|||
|
|||
try |
|||
{ |
|||
var elapsed = 0d; |
|||
var count = 0L; |
|||
|
|||
Console.WriteLine($"{selected.Name}: Initialized"); |
|||
|
|||
for (var run = 0; run < numRuns; run++) |
|||
{ |
|||
try |
|||
{ |
|||
selected.Benchmark.RunInitialize(); |
|||
|
|||
var watch = Stopwatch.StartNew(); |
|||
|
|||
count += selected.Benchmark.Run(); |
|||
|
|||
watch.Stop(); |
|||
|
|||
elapsed += watch.ElapsedMilliseconds; |
|||
|
|||
Console.WriteLine($"{selected.Name}: Run {run + 1} finished"); |
|||
} |
|||
finally |
|||
{ |
|||
selected.Benchmark.RunCleanup(); |
|||
} |
|||
} |
|||
|
|||
var averageElapsed = TimeSpan.FromMilliseconds(elapsed / numRuns); |
|||
var averageSeconds = Math.Round(count / (numRuns * averageElapsed.TotalSeconds), 2); |
|||
|
|||
Console.WriteLine($"{selected.Name}: Completed after {averageElapsed}, {averageSeconds} items/s"); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.WriteLine($"Benchmark failed with '{e.Message}'"); |
|||
} |
|||
} |
|||
|
|||
Console.ReadLine(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"profiles": { |
|||
"Benchmarks": { |
|||
"commandName": "Project" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
// ==========================================================================
|
|||
// Services.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Benchmarks.Tests.TestData; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using MongoDB.Driver; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Converters; |
|||
using NodaTime; |
|||
using NodaTime.Serialization.JsonNet; |
|||
using Squidex.Domain.Apps.Core.Apps.Json; |
|||
using Squidex.Domain.Apps.Core.Rules.Json; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Core.Schemas.Json; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Json; |
|||
using Squidex.Infrastructure.Log; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Benchmarks |
|||
{ |
|||
public static class Services |
|||
{ |
|||
public static IServiceProvider Create() |
|||
{ |
|||
var services = new ServiceCollection(); |
|||
|
|||
services.AddSingleton(CreateTypeNameRegistry()); |
|||
|
|||
services.AddSingleton<EventDataFormatter>(); |
|||
services.AddSingleton<FieldRegistry>(); |
|||
|
|||
services.AddTransient<MyAppState>(); |
|||
|
|||
services.AddSingleton<IMongoClient>( |
|||
new MongoClient("mongodb://localhost")); |
|||
|
|||
services.AddSingleton<ISemanticLog>( |
|||
new SemanticLog(new ILogChannel[0], new ILogAppender[0], () => new JsonLogWriter())); |
|||
|
|||
services.AddSingleton<IMemoryCache>( |
|||
new MemoryCache(Options.Create(new MemoryCacheOptions()))); |
|||
|
|||
services.AddSingleton<IPubSub, |
|||
InMemoryPubSub>(); |
|||
|
|||
services.AddSingleton<IEventNotifier, |
|||
DefaultEventNotifier>(); |
|||
|
|||
services.AddSingleton<IEventStore, |
|||
MongoEventStore>(); |
|||
|
|||
services.AddSingleton<IStateStore, |
|||
MongoStateStore>(); |
|||
|
|||
services.AddSingleton<IStateFactory, |
|||
StateFactory>(); |
|||
|
|||
services.AddSingleton<JsonSerializer>(c => |
|||
JsonSerializer.Create(c.GetRequiredService<JsonSerializerSettings>())); |
|||
|
|||
services.AddSingleton<JsonSerializerSettings>(c => |
|||
CreateJsonSerializerSettings(c.GetRequiredService<TypeNameRegistry>(), c.GetRequiredService<FieldRegistry>())); |
|||
|
|||
services.AddSingleton(c => |
|||
c.GetRequiredService<IMongoClient>().GetDatabase(Guid.NewGuid().ToString())); |
|||
|
|||
return services.BuildServiceProvider(); |
|||
} |
|||
|
|||
public static void Cleanup(this IServiceProvider services) |
|||
{ |
|||
var mongoClient = services.GetRequiredService<IMongoClient>(); |
|||
var mongoDatabase = services.GetRequiredService<IMongoDatabase>(); |
|||
|
|||
mongoClient.DropDatabase(mongoDatabase.DatabaseNamespace.DatabaseName); |
|||
|
|||
if (services is IDisposable disposable) |
|||
{ |
|||
disposable.Dispose(); |
|||
} |
|||
} |
|||
|
|||
private static TypeNameRegistry CreateTypeNameRegistry() |
|||
{ |
|||
var result = new TypeNameRegistry(); |
|||
|
|||
result.Map(typeof(MyEvent)); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private static JsonSerializerSettings CreateJsonSerializerSettings(TypeNameRegistry typeNameRegistry, FieldRegistry fieldRegistry) |
|||
{ |
|||
var settings = new JsonSerializerSettings(); |
|||
|
|||
settings.SerializationBinder = new TypeNameSerializationBinder(typeNameRegistry); |
|||
|
|||
settings.ContractResolver = new ConverterContractResolver( |
|||
new AppClientsConverter(), |
|||
new AppContributorsConverter(), |
|||
new ClaimsPrincipalConverter(), |
|||
new InstantConverter(), |
|||
new LanguageConverter(), |
|||
new LanguagesConfigConverter(), |
|||
new NamedGuidIdConverter(), |
|||
new NamedLongIdConverter(), |
|||
new NamedStringIdConverter(), |
|||
new PropertiesBagConverter<EnvelopeHeaders>(), |
|||
new PropertiesBagConverter<PropertiesBag>(), |
|||
new RefTokenConverter(), |
|||
new RuleConverter(), |
|||
new SchemaConverter(fieldRegistry), |
|||
new StringEnumConverter()); |
|||
|
|||
settings.NullValueHandling = NullValueHandling.Ignore; |
|||
|
|||
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat; |
|||
settings.DateParseHandling = DateParseHandling.None; |
|||
|
|||
settings.TypeNameHandling = TypeNameHandling.Auto; |
|||
|
|||
settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); |
|||
|
|||
BsonJsonConvention.Register(JsonSerializer.Create(settings)); |
|||
|
|||
return settings; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// ==========================================================================
|
|||
// AppendToEventStore.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Benchmarks.Utils; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Benchmarks.Tests |
|||
{ |
|||
public sealed class AppendToEventStore : IBenchmark |
|||
{ |
|||
private IServiceProvider services; |
|||
private IEventStore eventStore; |
|||
|
|||
public void RunInitialize() |
|||
{ |
|||
services = Services.Create(); |
|||
|
|||
eventStore = services.GetRequiredService<IEventStore>(); |
|||
} |
|||
|
|||
public long Run() |
|||
{ |
|||
const long numCommits = 100; |
|||
const long numStreams = 20; |
|||
|
|||
for (var streamId = 0; streamId < numStreams; streamId++) |
|||
{ |
|||
var eventOffset = -1; |
|||
var streamName = streamId.ToString(); |
|||
|
|||
for (var commitId = 0; commitId < numCommits; commitId++) |
|||
{ |
|||
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventOffset, new[] { Helper.CreateEventData() }).Wait(); |
|||
eventOffset++; |
|||
} |
|||
} |
|||
|
|||
return numCommits * numStreams; |
|||
} |
|||
|
|||
public void RunCleanup() |
|||
{ |
|||
services.Cleanup(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
// ==========================================================================
|
|||
// AppendToEventStoreWithManyWriters.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Benchmarks.Utils; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Benchmarks.Tests |
|||
{ |
|||
public sealed class AppendToEventStoreWithManyWriters : IBenchmark |
|||
{ |
|||
private IServiceProvider services; |
|||
private IEventStore eventStore; |
|||
|
|||
public void RunInitialize() |
|||
{ |
|||
services = Services.Create(); |
|||
|
|||
eventStore = services.GetRequiredService<IEventStore>(); |
|||
} |
|||
|
|||
public long Run() |
|||
{ |
|||
const long numCommits = 200; |
|||
const long numStreams = 100; |
|||
|
|||
Parallel.For(0, numStreams, streamId => |
|||
{ |
|||
var streamName = streamId.ToString(); |
|||
|
|||
for (var commitId = 0; commitId < numCommits; commitId++) |
|||
{ |
|||
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, new[] { Helper.CreateEventData() }).Wait(); |
|||
} |
|||
}); |
|||
|
|||
return numCommits * numStreams; |
|||
} |
|||
|
|||
public void RunCleanup() |
|||
{ |
|||
services.Cleanup(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// ==========================================================================
|
|||
// HandleEvents.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Benchmarks.Tests.TestData; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.CQRS.Events.Actors; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Benchmarks.Tests |
|||
{ |
|||
public sealed class HandleEvents : IBenchmark |
|||
{ |
|||
private const int NumEvents = 5000; |
|||
private IServiceProvider services; |
|||
private IEventStore eventStore; |
|||
private EventConsumerActor eventConsumerActor; |
|||
private EventDataFormatter eventDataFormatter; |
|||
private MyEventConsumer eventConsumer; |
|||
|
|||
public void RunInitialize() |
|||
{ |
|||
services = Services.Create(); |
|||
|
|||
eventConsumer = new MyEventConsumer(NumEvents); |
|||
|
|||
eventStore = services.GetRequiredService<IEventStore>(); |
|||
|
|||
eventDataFormatter = services.GetRequiredService<EventDataFormatter>(); |
|||
eventConsumerActor = services.GetRequiredService<EventConsumerActor>(); |
|||
|
|||
eventConsumerActor.ActivateAsync(services.GetRequiredService<StateHolder<EventConsumerState>>()).Wait(); |
|||
eventConsumerActor.Activate(eventConsumer); |
|||
} |
|||
|
|||
public long Run() |
|||
{ |
|||
var streamName = Guid.NewGuid().ToString(); |
|||
|
|||
for (var eventId = 0; eventId < NumEvents; eventId++) |
|||
{ |
|||
var eventData = eventDataFormatter.ToEventData(new Envelope<IEvent>(new MyEvent { EventNumber = eventId + 1 }), Guid.NewGuid()); |
|||
|
|||
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventId - 1, new[] { eventData }).Wait(); |
|||
} |
|||
|
|||
eventConsumer.WaitAndVerify(); |
|||
|
|||
return NumEvents; |
|||
} |
|||
|
|||
public void RunCleanup() |
|||
{ |
|||
services.Cleanup(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
// ==========================================================================
|
|||
// HandleEventsWithManyWriters.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Benchmarks.Tests.TestData; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.CQRS.Events.Actors; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Benchmarks.Tests |
|||
{ |
|||
public sealed class HandleEventsWithManyWriters : IBenchmark |
|||
{ |
|||
private const int NumCommits = 200; |
|||
private const int NumStreams = 10; |
|||
private IServiceProvider services; |
|||
private IEventStore eventStore; |
|||
private EventConsumerActor eventConsumerActor; |
|||
private EventDataFormatter eventDataFormatter; |
|||
private MyEventConsumer eventConsumer; |
|||
|
|||
public void RunInitialize() |
|||
{ |
|||
services = Services.Create(); |
|||
|
|||
eventConsumer = new MyEventConsumer(NumStreams * NumCommits); |
|||
|
|||
eventStore = services.GetRequiredService<IEventStore>(); |
|||
|
|||
eventDataFormatter = services.GetRequiredService<EventDataFormatter>(); |
|||
eventConsumerActor = services.GetRequiredService<EventConsumerActor>(); |
|||
|
|||
eventConsumerActor.ActivateAsync(services.GetRequiredService<StateHolder<EventConsumerState>>()).Wait(); |
|||
eventConsumerActor.Activate(eventConsumer); |
|||
} |
|||
|
|||
public long Run() |
|||
{ |
|||
Parallel.For(0, NumStreams, streamId => |
|||
{ |
|||
var eventOffset = -1; |
|||
var streamName = streamId.ToString(); |
|||
|
|||
for (var commitId = 0; commitId < NumCommits; commitId++) |
|||
{ |
|||
var eventData = eventDataFormatter.ToEventData(new Envelope<IEvent>(new MyEvent()), Guid.NewGuid()); |
|||
|
|||
eventStore.AppendEventsAsync(Guid.NewGuid(), streamName, eventOffset - 1, new[] { eventData }).Wait(); |
|||
eventOffset++; |
|||
} |
|||
}); |
|||
|
|||
eventConsumer.WaitAndVerify(); |
|||
|
|||
return NumStreams * NumCommits; |
|||
} |
|||
|
|||
public void RunCleanup() |
|||
{ |
|||
services.Cleanup(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,102 @@ |
|||
// ==========================================================================
|
|||
// ReadSchemaState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Benchmarks.Tests.TestData; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using NodaTime; |
|||
using Squidex.Domain.Apps.Core; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
using Squidex.Domain.Apps.Core.Rules.Actions; |
|||
using Squidex.Domain.Apps.Core.Rules.Triggers; |
|||
using Squidex.Domain.Apps.Core.Schemas; |
|||
using Squidex.Domain.Apps.Read.State.Orleans.Grains; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Benchmarks.Tests |
|||
{ |
|||
public class ReadSchemaState : IBenchmark |
|||
{ |
|||
private IServiceProvider services; |
|||
private MyAppState grain; |
|||
|
|||
public void RunInitialize() |
|||
{ |
|||
services = Services.Create(); |
|||
|
|||
grain = services.GetRequiredService<IStateFactory>().GetAsync<MyAppState, AppStateGrainState>("DEFAULT").Result; |
|||
|
|||
var state = new AppStateGrainState |
|||
{ |
|||
App = new JsonAppEntity |
|||
{ |
|||
Id = Guid.NewGuid() |
|||
} |
|||
}; |
|||
|
|||
state.Schemas = new Dictionary<Guid, JsonSchemaEntity>(); |
|||
|
|||
for (var i = 1; i <= 100; i++) |
|||
{ |
|||
var schema = new JsonSchemaEntity |
|||
{ |
|||
Id = Guid.NewGuid(), |
|||
Created = SystemClock.Instance.GetCurrentInstant(), |
|||
CreatedBy = new RefToken("user", "1"), |
|||
LastModified = SystemClock.Instance.GetCurrentInstant(), |
|||
LastModifiedBy = new RefToken("user", "1"), |
|||
SchemaDef = new Schema("Name") |
|||
}; |
|||
|
|||
for (var j = 1; j < 30; j++) |
|||
{ |
|||
schema.SchemaDef = schema.SchemaDef.AddField(new StringField(j, j.ToString(), Partitioning.Invariant)); |
|||
} |
|||
|
|||
state.Schemas.Add(schema.Id, schema); |
|||
} |
|||
|
|||
state.Rules = new Dictionary<Guid, JsonRuleEntity>(); |
|||
|
|||
for (var i = 0; i < 100; i++) |
|||
{ |
|||
var rule = new JsonRuleEntity |
|||
{ |
|||
Id = Guid.NewGuid(), |
|||
Created = SystemClock.Instance.GetCurrentInstant(), |
|||
CreatedBy = new RefToken("user", "1"), |
|||
LastModified = SystemClock.Instance.GetCurrentInstant(), |
|||
LastModifiedBy = new RefToken("user", "1"), |
|||
RuleDef = new Rule(new ContentChangedTrigger(), new WebhookAction()) |
|||
}; |
|||
|
|||
state.Rules.Add(rule.Id, rule); |
|||
} |
|||
|
|||
grain.SetState(state); |
|||
grain.WriteStateAsync().Wait(); |
|||
} |
|||
|
|||
public long Run() |
|||
{ |
|||
for (var i = 0; i < 10; i++) |
|||
{ |
|||
grain.ReadStateAsync().Wait(); |
|||
} |
|||
|
|||
return 10; |
|||
} |
|||
|
|||
public void RunCleanup() |
|||
{ |
|||
services.Cleanup(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// ReadSchemaState.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Domain.Apps.Read.State.Orleans.Grains; |
|||
using Squidex.Infrastructure.States; |
|||
|
|||
namespace Benchmarks.Tests.TestData |
|||
{ |
|||
public sealed class MyAppState : StatefulObject<AppStateGrainState> |
|||
{ |
|||
public void SetState(AppStateGrainState state) |
|||
{ |
|||
State = state; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// MyEvent.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Benchmarks.Tests.TestData |
|||
{ |
|||
[TypeName("MyEvent")] |
|||
public sealed class MyEvent : IEvent |
|||
{ |
|||
public int EventNumber { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// ==========================================================================
|
|||
// MyEventConsumer.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
using Squidex.Infrastructure.Tasks; |
|||
|
|||
namespace Benchmarks.Tests.TestData |
|||
{ |
|||
public sealed class MyEventConsumer : IEventConsumer |
|||
{ |
|||
private readonly TaskCompletionSource<object> completion = new TaskCompletionSource<object>(); |
|||
private readonly int numEvents; |
|||
|
|||
public List<int> EventNumbers { get; } = new List<int>(); |
|||
|
|||
public string Name |
|||
{ |
|||
get { return typeof(MyEventConsumer).Name; } |
|||
} |
|||
|
|||
public string EventsFilter |
|||
{ |
|||
get { return string.Empty; } |
|||
} |
|||
|
|||
public MyEventConsumer(int numEvents) |
|||
{ |
|||
this.numEvents = numEvents; |
|||
} |
|||
|
|||
public Task ClearAsync() |
|||
{ |
|||
return TaskHelper.Done; |
|||
} |
|||
|
|||
public void WaitAndVerify() |
|||
{ |
|||
completion.Task.Wait(); |
|||
|
|||
if (EventNumbers.Count != numEvents) |
|||
{ |
|||
throw new InvalidOperationException($"{EventNumbers.Count} Events have been handled"); |
|||
} |
|||
|
|||
for (var i = 0; i < EventNumbers.Count; i++) |
|||
{ |
|||
var value = EventNumbers[i]; |
|||
|
|||
if (value != i + 1) |
|||
{ |
|||
throw new InvalidOperationException($"Event[{i}] != value"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public Task On(Envelope<IEvent> @event) |
|||
{ |
|||
if (@event.Payload is MyEvent myEvent) |
|||
{ |
|||
EventNumbers.Add(myEvent.EventNumber); |
|||
|
|||
if (myEvent.EventNumber == numEvents) |
|||
{ |
|||
completion.SetResult(true); |
|||
} |
|||
} |
|||
|
|||
return TaskHelper.Done; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// ==========================================================================
|
|||
// Helper.cs
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using Squidex.Infrastructure.CQRS.Events; |
|||
|
|||
namespace Benchmarks.Utils |
|||
{ |
|||
public static class Helper |
|||
{ |
|||
public static EventData CreateEventData() |
|||
{ |
|||
return new EventData { EventId = Guid.NewGuid(), Metadata = "EventMetdata", Payload = "EventPayload", Type = "MyEvent" }; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue