diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index b1f10f5a7b..316c1fffeb 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -68,6 +68,25 @@ methods. Avoid depending on undocumented authentication plugins. Use [OpenTelemetry integration](diagnostics/integrations.md) for explicit OTLP export and [Metrics](diagnostics/metrics.md) for Prometheus scraping. +## TestClient review + +The TestClient keeps command names only when their observable behavior can be +preserved over the supported gRPC APIs. Reads, writes, hard deletes, +subscriptions, scavenging, data verification, and load operations use the gRPC +client on `NodePort`. The generic `WRFL` alias remains available and runs the +same workload as `WRFLGRPC`. + +The following commands are intentionally retired: + +- `TWR`, because the public gRPC streams API does not expose the transaction + start, write, and commit lifecycle. A batch append is not an equivalent test. +- `WRFLTCP` and `WRFLCA`, because transport-specific aliases would hide which + protocol the workload exercises. +- `RT`, because its projection and node-failure scenarios require a dedicated + replacement rather than a different command behind the same name. +- `CHKTCP`, because it validates a retired frame protocol. Use `CHKGRPC` to + validate malformed gRPC-frame handling. + Legacy usage telemetry is separate from OTLP observability. See [Usage telemetry](usage-telemetry.md) before running a node in an environment that should not make outbound telemetry calls. diff --git a/scripts/test.sh b/scripts/test.sh index 54c3fc0363..7e90473284 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -52,6 +52,7 @@ misc_projects=( EventStore.Common.Tests EventStore.SourceGenerators.Tests EventStore.SystemRuntime.Tests + EventStore.TestClient.Tests ) declare -a requested_projects diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index cf693dd4db..4ba5fa1a9f 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -12,6 +12,7 @@ + diff --git a/src/EventStore.TestClient.Tests/CommandInventoryTests.cs b/src/EventStore.TestClient.Tests/CommandInventoryTests.cs new file mode 100644 index 0000000000..5dc4d34ce1 --- /dev/null +++ b/src/EventStore.TestClient.Tests/CommandInventoryTests.cs @@ -0,0 +1,44 @@ +using System; +using System.Linq; +using System.Threading; +using NUnit.Framework; + +namespace EventStore.TestClient.Tests; + +[TestFixture] +public class CommandInventoryTests +{ + private static readonly string[] SupportedCommandKeywords = + [ + "PING", "PINGFL", "PINGFLW", "RDALLGRPC", "WRFLGRPC", + "WR", "WRJ", "WRFL", "WRFLW", + "MWR", "MWRFLW", "DEL", + "RDALL", "RD", "RDFL", "WRLT", + "VERIFY", "SUBSCR", "SCAVENGE", "CHKGRPC", "SST" + ]; + private static readonly string[] RetiredCommandKeywords = ["TWR", "WRFLCA", "WRFLTCP", "RT", "CHKTCP"]; + + [Test] + public void test_client_preserves_supported_command_surface() + { + using var cancellation = new CancellationTokenSource(); + var client = new Client(new ClientOptions(), cancellation); + var usages = client.GetCommandList() + .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + Assert.Multiple(() => + { + foreach (var keyword in SupportedCommandKeywords) + { + Assert.That(usages.Any(x => x == keyword || x.StartsWith($"{keyword} ", StringComparison.Ordinal)), + Is.True, $"Missing TestClient command '{keyword}'"); + } + + foreach (var keyword in RetiredCommandKeywords) + { + Assert.That(usages.Any(x => x == keyword || x.StartsWith($"{keyword} ", StringComparison.Ordinal)), + Is.False, $"TestClient command '{keyword}' advertises behavior that gRPC does not provide"); + } + }); + } +} diff --git a/src/EventStore.TestClient.Tests/EventStore.TestClient.Tests.csproj b/src/EventStore.TestClient.Tests/EventStore.TestClient.Tests.csproj new file mode 100644 index 0000000000..2cb72fad1a --- /dev/null +++ b/src/EventStore.TestClient.Tests/EventStore.TestClient.Tests.csproj @@ -0,0 +1,20 @@ + + + true + + + + + + + + + + + + + version.properties + PreserveNewest + + + diff --git a/src/EventStore.TestClient.Tests/GrpcCommandBehaviorTests.cs b/src/EventStore.TestClient.Tests/GrpcCommandBehaviorTests.cs new file mode 100644 index 0000000000..e015876eee --- /dev/null +++ b/src/EventStore.TestClient.Tests/GrpcCommandBehaviorTests.cs @@ -0,0 +1,571 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using EventStore.Client; +using EventStore.TestClient.GrpcCommands; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace EventStore.TestClient.Tests; + +[TestFixture] +public class GrpcCommandBehaviorTests +{ + [Test] + public void grpc_workloads_keep_the_existing_backpressure_defaults() + { + var options = new ClientOptions(); + + Assert.Multiple(() => + { + Assert.That(options.PingWindow, Is.EqualTo(2_000)); + Assert.That(options.ReadWindow, Is.EqualTo(2_000)); + Assert.That(options.WriteWindow, Is.EqualTo(2_000)); + }); + } + + [Test] + public void ping_flood_defaults_preserve_the_historical_total_request_count() + { + var workload = FloodWorkload.Parse([], defaultRequestCount: 1_000_000, maxInFlight: 2_000); + + Assert.Multiple(() => + { + Assert.That(workload.ClientCount, Is.EqualTo(1)); + Assert.That(workload.RequestCount, Is.EqualTo(1_000_000)); + Assert.That(workload.RequestsForClient(0), Is.EqualTo(1_000_000)); + }); + } + + [Test] + public void ping_flood_waiting_defaults_preserve_the_historical_total_request_count() + { + var workload = FloodWorkload.Parse([], defaultRequestCount: 100_000, maxInFlight: 1); + + Assert.That(workload.RequestCount, Is.EqualTo(100_000)); + } + + [Test] + public void flood_requests_are_distributed_as_one_total_across_clients() + { + var workload = FloodWorkload.Parse(["4", "10"], defaultRequestCount: 1, maxInFlight: 2_000); + + Assert.Multiple(() => + { + Assert.That(workload.RequestsForClient(0), Is.EqualTo(2)); + Assert.That(workload.RequestsForClient(1), Is.EqualTo(2)); + Assert.That(workload.RequestsForClient(2), Is.EqualTo(2)); + Assert.That(workload.RequestsForClient(3), Is.EqualTo(4)); + Assert.That(workload.MaxInFlightPerClient, Is.EqualTo(500)); + }); + } + + [TestCase(true, "True")] + [TestCase(false, "False")] + public async Task read_commands_control_the_requires_leader_header(bool requireLeader, string expectedHeader) + { + var handler = new RecordingGrpcHandler(); + var grpcClient = new GrpcTestClient(new ClientOptions(), Serilog.Log.Logger, () => + { + var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + settings.CreateHttpMessageHandler = () => handler; + return settings; + }); + using var client = grpcClient.CreateGrpcClient(requireLeader); + var read = client.ReadStreamAsync(Direction.Forwards, "test-stream", StreamPosition.Start, maxCount: 1); + var state = await read.ReadState; + + Assert.Multiple(() => + { + Assert.That(state, Is.EqualTo(ReadState.StreamNotFound)); + Assert.That(handler.Requests.Single(request => request.Path.EndsWith("/Read", StringComparison.Ordinal)).RequiresLeader, + Is.EqualTo(expectedHeader)); + }); + } + + [TestCase(0, "AccountDebited")] + [TestCase(1, "AccountCredited")] + [TestCase(9, "AccountCheckPoint")] + public void verification_events_preserve_deterministic_typed_json(int eventNumber, string expectedType) + { + var first = VerificationEventFactory.Create(eventNumber); + var second = VerificationEventFactory.Create(eventNumber); + + Assert.Multiple(() => + { + Assert.That(first.Type, Is.EqualTo(expectedType)); + Assert.That(first.ContentType, Is.EqualTo("application/json")); + Assert.That(first.Data.ToArray(), Is.EqualTo(second.Data.ToArray())); + Assert.That(() => JObject.Parse(System.Text.Encoding.UTF8.GetString(first.Data.Span)), Throws.Nothing); + }); + } + + [Test] + public void delete_command_uses_the_grpc_tombstone_operation() + { + var handler = new RecordingGrpcHandler(); + var options = new ClientOptions { Command = ["DEL test-stream ANY"] }; + var grpcClient = new GrpcTestClient(options, Serilog.Log.Logger, () => + { + var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + settings.CreateHttpMessageHandler = () => handler; + return settings; + }); + using var cancellation = new CancellationTokenSource(); + var client = new Client(options, cancellation, grpcClient); + + Assert.Multiple(() => + { + Assert.That(client.Run(cancellation.Token), Is.Zero); + Assert.That(handler.Requests.Select(request => request.Path), + Does.Contain("/event_store.client.streams.Streams/Tombstone")); + Assert.That(handler.Requests.Select(request => request.Path), + Does.Not.Contain("/event_store.client.streams.Streams/Delete")); + }); + } + + [Test] + public void ping_flood_treats_messages_as_one_total_across_clients() + { + var handler = new RecordingGrpcHandler(); + var options = new ClientOptions { Command = ["PINGFL 4 10"] }; + var grpcClient = new GrpcTestClient(options, Serilog.Log.Logger, () => + { + var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + settings.CreateHttpMessageHandler = () => handler; + return settings; + }); + using var cancellation = new CancellationTokenSource(); + var client = new Client(options, cancellation, grpcClient); + + Assert.Multiple(() => + { + Assert.That(client.Run(cancellation.Token), Is.Zero); + Assert.That(handler.Requests.Count(request => request.Path.EndsWith("/Read", StringComparison.Ordinal)), + Is.EqualTo(10)); + }); + } + + [TestCase("WRFLW 2 4")] + [TestCase("MWRFLW 2 2 4")] + public void waiting_write_floods_finish_every_request_before_reporting_failures(string command) + { + var handler = new RecordingGrpcHandler + { + Failure = new HttpRequestException("first append failed"), + FailurePath = "/event_store.client.streams.Streams/Append", + FailureLimit = 1 + }; + + var result = RunCommand(command, handler); + + Assert.Multiple(() => + { + Assert.That(result, Is.Not.Zero); + Assert.That(handler.Requests.Count(request => request.Path.EndsWith("/Append", StringComparison.Ordinal)), + Is.EqualTo(4)); + }); + } + + [TestCase("WRFLW 1 3G")] + [TestCase("MWRFLW 1 1 3G")] + public void waiting_write_floods_accept_long_request_totals(string command) + { + using var cancellation = new CancellationTokenSource(); + var handler = new RecordingGrpcHandler + { + OnRequest = request => + { + if (request.RequestUri!.AbsolutePath.EndsWith("/Append", StringComparison.Ordinal)) + { + cancellation.Cancel(); + } + } + }; + + Assert.Multiple(() => + { + Assert.That( + () => RunCommand(command, handler, new ClientOptions { Timeout = 2 }, cancellation), + Throws.InstanceOf()); + Assert.That(handler.Requests.Count(request => request.Path.EndsWith("/Append", StringComparison.Ordinal)), + Is.EqualTo(1)); + }); + } + + [Test] + public void ping_uses_a_user_stream_for_unauthenticated_connectivity_checks() + { + var handler = new RecordingGrpcHandler(); + var result = RunCommand("PING", handler); + var request = handler.Requests.Single(request => request.Path.EndsWith("/Read", StringComparison.Ordinal)); + + Assert.Multiple(() => + { + Assert.That(result, Is.Zero); + Assert.That(Encoding.UTF8.GetString(request.Body), Does.Not.Contain("$test-client-ping")); + }); + } + + [TestCase("SUBSCR", true, null)] + [TestCase("SUBSCR test-stream", false, "test-stream")] + [TestCase("SST 1", false, "stream-0")] + public async Task subscriptions_start_from_the_live_position(string command, bool subscribeToAll, string stream) + { + var expected = await RecordSubscriptionRequest(subscribeToAll, stream); + var handler = new RecordingGrpcHandler + { + Failure = new HttpRequestException("stop after recording"), + FailurePath = "/event_store.client.streams.Streams/Read" + }; + + RunCommand(command, handler); + + Assert.That( + handler.Requests.Single(request => request.Path == "/event_store.client.streams.Streams/Read").Body, + Is.EqualTo(expected)); + } + + [TestCase("SUBSCR test-stream")] + [TestCase("SST 1")] + public void subscriptions_report_server_drops(string command) + { + var handler = new RecordingGrpcHandler + { + ReadPayload = [0x12, 0x00], + GrpcStatus = "13" + }; + var result = 0; + + Assert.That( + () => result = RunCommand(command, handler, new ClientOptions { Timeout = 1 }), + Throws.Nothing); + Assert.That(result, Is.Not.Zero); + } + + [Test] + public void read_flood_completes_every_request_before_reporting_missing_streams() + { + var handler = new RecordingGrpcHandler(); + var result = RunCommand("RDFL 2 5 1 missing-stream", handler, new ClientOptions { ReadWindow = 1 }); + + Assert.Multiple(() => + { + Assert.That(result, Is.Not.Zero); + Assert.That(handler.Requests.Count(request => request.Path.EndsWith("/Read", StringComparison.Ordinal)), + Is.EqualTo(5)); + }); + } + + [Test] + public void read_command_fails_when_the_requested_event_is_missing() + { + var handler = new RecordingGrpcHandler { ReadPayload = [] }; + + Assert.That(RunCommand("RD test-stream 42", handler), Is.Not.Zero); + } + + [Test] + public async Task read_command_preserves_the_last_event_sentinel() + { + var expected = await RecordStreamReadRequest(Direction.Backwards, StreamPosition.End); + var handler = new RecordingGrpcHandler + { + Failure = new HttpRequestException("stop after recording"), + FailurePath = "/event_store.client.streams.Streams/Read" + }; + + RunCommand("RD test-stream -1", handler); + + Assert.That( + handler.Requests.Single(request => request.Path == "/event_store.client.streams.Streams/Read").Body, + Is.EqualTo(expected)); + } + + [Test] + public async Task read_all_accepts_explicit_end_position_sentinels() + { + var expected = await RecordReadAllRequest(Direction.Backwards, Position.End); + var handler = new RecordingGrpcHandler { ReadPayload = [] }; + + var result = RunCommand("RDALL B -1 -1", handler); + + Assert.Multiple(() => + { + Assert.That(result, Is.Zero); + Assert.That( + handler.Requests.Single(request => request.Path == "/event_store.client.streams.Streams/Read").Body, + Is.EqualTo(expected)); + }); + } + + [Test] + public void read_all_rejects_unknown_directions() + { + var handler = new RecordingGrpcHandler { ReadPayload = [] }; + + var result = RunCommand("RDALL SIDEWAYS", handler); + + Assert.Multiple(() => + { + Assert.That(result, Is.Not.Zero); + Assert.That(handler.Requests.Select(request => request.Path), + Does.Not.Contain("/event_store.client.streams.Streams/Read")); + }); + } + + [TestCase("-1", "NOSTREAM")] + [TestCase("-2", "ANY")] + public void expected_version_sentinels_preserve_their_historical_meaning(string sentinel, string name) + { + var expectedHandler = new RecordingGrpcHandler(); + Assert.That(RunCommand($"DEL test-stream {name}", expectedHandler), Is.Zero); + + var sentinelHandler = new RecordingGrpcHandler(); + Assert.That(RunCommand($"DEL test-stream {sentinel}", sentinelHandler), Is.Zero); + + Assert.That( + sentinelHandler.Requests.Single(request => request.Path.EndsWith("/Tombstone", StringComparison.Ordinal)).Body, + Is.EqualTo(expectedHandler.Requests.Single(request => request.Path.EndsWith("/Tombstone", StringComparison.Ordinal)).Body)); + } + + [Test] + public void grpc_http_endpoint_uses_the_resolved_single_node_address() + { + var settings = EventStoreClientSettings.Create("esdb://configured.example:3210?tls=false"); + var grpcClient = new GrpcTestClient( + new ClientOptions { Host = "ignored.example", HttpPort = 9999 }, + Serilog.Log.Logger, + () => settings); + + Assert.That(grpcClient.HttpEndpoint, Is.EqualTo(settings.ConnectivitySettings.Address)); + } + + [TestCase("esdb+discover://localhost:2113?tls=false")] + [TestCase("esdb://node-1:2113,node-2:2113?tls=false")] + public void grpc_http_endpoint_rejects_connections_without_one_resolved_address(string connectionString) + { + var settings = EventStoreClientSettings.Create(connectionString); + var grpcClient = new GrpcTestClient(new ClientOptions(), Serilog.Log.Logger, () => settings); + + Assert.That(() => grpcClient.HttpEndpoint, Throws.InvalidOperationException); + } + + [Test] + public void what_if_options_do_not_render_connection_string_credentials() + { + var rendered = new ClientOptions + { + ConnectionString = "esdb://test-user:test-password@localhost:2113?tls=false" + }.ToString(); + + Assert.Multiple(() => + { + Assert.That(rendered, Does.Not.Contain("test-user")); + Assert.That(rendered, Does.Not.Contain("test-password")); + Assert.That(rendered, Does.Contain("ConnectionString: [REDACTED]")); + }); + } + + [Test] + public void verification_readers_wait_without_reposting_while_streams_are_empty() + { + var handler = new RecordingGrpcHandler + { + Delay = TimeSpan.FromMilliseconds(100), + DelayPath = "/event_store.client.streams.Streams/Append" + }; + var completedWorkItems = ThreadPool.CompletedWorkItemCount; + var command = Task.Run(() => + RunCommand("VERIFY 1 2 1 1 bank", handler, new ClientOptions { Timeout = 2 })); + + Assert.Multiple(() => + { + Assert.That(command.Wait(TimeSpan.FromSeconds(5)), Is.True); + Assert.That(handler.Requests.Select(request => request.Path), Does.Contain(handler.DelayPath)); + Assert.That(ThreadPool.CompletedWorkItemCount - completedWorkItems, Is.LessThan(100)); + }); + } + + [Test] + public void grpc_read_all_preserves_backward_direction_and_default_position() + { + Assert.That(ReadAllWorkload.TryParse(["B", "1"], out var workload), Is.True); + Assert.Multiple(() => + { + Assert.That(workload.Direction, Is.EqualTo(Direction.Backwards)); + Assert.That(workload.Position, Is.EqualTo(Position.End)); + Assert.That(workload.ClientCount, Is.EqualTo(1)); + }); + } + + private static int RunCommand( + string command, + RecordingGrpcHandler handler, + ClientOptions options = null, + CancellationTokenSource cancellation = null) + { + options = (options ?? new ClientOptions()) with { Command = [command] }; + var grpcClient = new GrpcTestClient(options, Serilog.Log.Logger, () => + { + var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + settings.CreateHttpMessageHandler = () => handler; + return settings; + }); + var ownsCancellation = cancellation is null; + cancellation ??= new CancellationTokenSource(); + try + { + return new Client(options, cancellation, grpcClient).Run(cancellation.Token); + } + finally + { + if (ownsCancellation) + { + cancellation.Dispose(); + } + } + } + + private static async Task RecordSubscriptionRequest(bool subscribeToAll, string stream) + { + var handler = new RecordingGrpcHandler + { + Failure = new HttpRequestException("stop after recording"), + FailurePath = "/event_store.client.streams.Streams/Read" + }; + var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + settings.CreateHttpMessageHandler = () => handler; + using var client = new EventStoreClient(settings); + try + { + if (subscribeToAll) + { + await client.SubscribeToAllAsync(FromAll.End, (_, _, _) => Task.CompletedTask); + } + else + { + await client.SubscribeToStreamAsync(stream, FromStream.End, (_, _, _) => Task.CompletedTask); + } + } + catch (Grpc.Core.RpcException) + { + } + + return handler.Requests.Single(request => request.Path == "/event_store.client.streams.Streams/Read").Body; + } + + private static async Task RecordStreamReadRequest(Direction direction, StreamPosition position) + { + var handler = new RecordingGrpcHandler + { + Failure = new HttpRequestException("stop after recording"), + FailurePath = "/event_store.client.streams.Streams/Read" + }; + using var client = CreateClient(handler); + try + { + var read = client.ReadStreamAsync(direction, "test-stream", position, maxCount: 1); + await read.ReadState; + } + catch (Grpc.Core.RpcException) + { + } + + return handler.Requests.Single(request => request.Path == "/event_store.client.streams.Streams/Read").Body; + } + + private static async Task RecordReadAllRequest(Direction direction, Position position) + { + var handler = new RecordingGrpcHandler + { + Failure = new HttpRequestException("stop after recording"), + FailurePath = "/event_store.client.streams.Streams/Read" + }; + using var client = CreateClient(handler); + try + { + var read = client.ReadAllAsync(direction, position); + await foreach (var _ in read.Messages) + { + } + } + catch (Grpc.Core.RpcException) + { + } + + return handler.Requests.Single(request => request.Path == "/event_store.client.streams.Streams/Read").Body; + } + + private static EventStoreClient CreateClient(HttpMessageHandler handler) + { + var settings = EventStoreClientSettings.Create("esdb://localhost:2113?tls=false"); + settings.CreateHttpMessageHandler = () => handler; + return new EventStoreClient(settings); + } + + private sealed class RecordingGrpcHandler : HttpMessageHandler + { + private int _failuresIssued; + + public ConcurrentBag Requests { get; } = []; + public TimeSpan Delay { get; init; } + public string DelayPath { get; init; } + public Exception Failure { get; init; } + public int? FailureLimit { get; init; } + public string FailurePath { get; init; } + public Action OnRequest { get; init; } + public byte[] ReadPayload { get; init; } = [0x22, 0x00]; + public string GrpcStatus { get; init; } = "0"; + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var path = request.RequestUri!.AbsolutePath; + if (Delay > TimeSpan.Zero && (DelayPath is null || path == DelayPath)) + { + await Task.Delay(Delay, cancellationToken); + } + + var requiresLeader = request.Headers.TryGetValues("requires-leader", out var values) + ? values.Single() + : null; + var body = request.Content is null + ? [] + : await request.Content.ReadAsByteArrayAsync(cancellationToken); + Requests.Add(new RecordedGrpcRequest(path, requiresLeader, body)); + OnRequest?.Invoke(request); + cancellationToken.ThrowIfCancellationRequested(); + if (Failure is not null && (FailurePath is null || path == FailurePath) && + (FailureLimit is null || Interlocked.Increment(ref _failuresIssued) <= FailureLimit)) + { + throw Failure; + } + + var payload = path.EndsWith("/Tombstone", StringComparison.Ordinal) + ? new byte[] { 0x0a, 0x00 } + : path.EndsWith("/Read", StringComparison.Ordinal) + ? ReadPayload + : []; + var frame = new byte[payload.Length + 5]; + frame[4] = (byte)payload.Length; + payload.CopyTo(frame, 5); + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Version = HttpVersion.Version20, + Content = new ByteArrayContent(frame) + }; + response.Content.Headers.ContentType = new("application/grpc"); + response.TrailingHeaders.Add("grpc-status", GrpcStatus); + return response; + } + } + + private sealed record RecordedGrpcRequest(string Path, string RequiresLeader, byte[] Body); +} diff --git a/src/EventStore.TestClient/Client.cs b/src/EventStore.TestClient/Client.cs index 28385ba8a0..faf7d995c2 100644 --- a/src/EventStore.TestClient/Client.cs +++ b/src/EventStore.TestClient/Client.cs @@ -4,8 +4,6 @@ using System.Threading; using EventStore.Common.Utils; using EventStore.TestClient.Commands; -using EventStore.TestClient.Commands.DvuBasic; -using Connection = EventStore.Transport.Tcp.TcpTypedConnection; using ILogger = Serilog.ILogger; #pragma warning disable 1591 @@ -20,23 +18,25 @@ public class Client public readonly ClientOptions Options; - public readonly TcpTestClient _tcpTestClient; public readonly GrpcTestClient _grpcTestClient; - public readonly ClientApiTcpTestClient _clientApiTestClient; private readonly CommandsProcessor _commands = new CommandsProcessor(Log); public Client(ClientOptions options, CancellationTokenSource cancellationTokenSource) + : this(options, cancellationTokenSource, null) { - Options = options; + } - var interactiveMode = options.Command.IsEmpty(); + internal Client( + ClientOptions options, + CancellationTokenSource cancellationTokenSource, + GrpcTestClient grpcTestClient) + { + Options = options; InteractiveMode = options.Command.IsEmpty(); - _tcpTestClient = new TcpTestClient(options, interactiveMode, Log); - _grpcTestClient = new GrpcTestClient(options, Log); - _clientApiTestClient = new ClientApiTcpTestClient(options, Log); + _grpcTestClient = grpcTestClient ?? new GrpcTestClient(options, Log); RegisterProcessors(cancellationTokenSource); } @@ -46,46 +46,19 @@ private void RegisterProcessors(CancellationTokenSource cancellationTokenSource) _commands.Register(new UsageProcessor(_commands), usageProcessor: true); _commands.Register(new ExitProcessor(cancellationTokenSource)); - _commands.Register(new PingProcessor()); - _commands.Register(new PingFloodProcessor()); - _commands.Register(new PingFloodWaitingProcessor()); - - _commands.Register(new WriteProcessor()); - _commands.Register(new WriteJsonProcessor()); - _commands.Register(new WriteFloodProcessor()); - _commands.Register(new WriteFloodClientApiProcessor()); - _commands.Register(new WriteFloodWaitingProcessor()); - - _commands.Register(new MultiWriteProcessor()); - _commands.Register(new MultiWriteFloodWaitingProcessor()); - - _commands.Register(new TransactionWriteProcessor()); - - _commands.Register(new DeleteProcessor()); - - _commands.Register(new ReadAllProcessor()); - _commands.Register(new ReadProcessor()); - _commands.Register(new ReadFloodProcessor()); - - _commands.Register(new WriteLongTermProcessor()); - - _commands.Register(new DvuBasicProcessor()); - _commands.Register(new RunTestScenariosProcessor()); - - _commands.Register(new SubscribeToStreamProcessor()); - - _commands.Register(new ScavengeProcessor()); - - _commands.Register(new TcpSanitazationCheckProcessor()); - - _commands.Register(new SubscriptionStressTestProcessor()); - - // gRPC _commands.Register(new GrpcCommands.ReadAllProcessor()); - _commands.Register(new GrpcCommands.WriteFloodProcessor()); + var writeFlood = new GrpcCommands.WriteFloodProcessor(); + _commands.Register(writeFlood); + + foreach (var processor in GrpcCommands.CompatibilityProcessor.CreateSupportedProcessors()) + { + _commands.Register(processor); + } - // TCP Client API - _commands.Register(new ClientApiTcpCommands.WriteFloodProcessor()); + _commands.Register(new GrpcCommands.DelegatingProcessor( + "WRFL", + "WRFL [ [ [ [ []]]]]", + writeFlood)); } public string GetCommandList() @@ -149,7 +122,7 @@ private int Execute(string[] args, CancellationToken cancellationToken) { Log.Information("Processing command: {command}.", string.Join(" ", args)); - var context = new CommandProcessorContext(_tcpTestClient, _grpcTestClient, _clientApiTestClient, Options.Timeout, + var context = new CommandProcessorContext(_grpcTestClient, Options.Timeout, Log, Options.StatsLog, Options.OutputCsv, new ManualResetEventSlim(true), cancellationToken); int exitCode; diff --git a/src/EventStore.TestClient/ClientApiLoggerBridge.cs b/src/EventStore.TestClient/ClientApiLoggerBridge.cs deleted file mode 100644 index a7dc5f5bd1..0000000000 --- a/src/EventStore.TestClient/ClientApiLoggerBridge.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using EventStore.Common.Utils; -using ILogger = Serilog.ILogger; - -namespace EventStore.TestClient; - -internal class ClientApiLoggerBridge : ClientAPI.ILogger -{ - public static readonly ClientApiLoggerBridge Default = - new ClientApiLoggerBridge(Serilog.Log.ForContext(Serilog.Core.Constants.SourceContextPropertyName, - "client-api")); - - private readonly Serilog.ILogger _log; - - public ClientApiLoggerBridge(ILogger log) - { - Ensure.NotNull(log, "log"); - _log = log; - } - - public void Error(string format, params object[] args) - { - if (args.Length == 0) - { - _log.Error(format); - } - else - { - _log.Error(format, args); - } - } - - public void Error(Exception ex, string format, params object[] args) - { - if (args.Length == 0) - { - _log.Error(ex, format); - } - else - { - _log.Error(ex, format, args); - } - } - - public void Info(string format, params object[] args) - { - if (args.Length == 0) - { - _log.Information(format); - } - else - { - _log.Information(format, args); - } - } - - public void Info(Exception ex, string format, params object[] args) - { - if (args.Length == 0) - { - _log.Information(ex, format); - } - else - { - _log.Information(ex, format, args); - } - } - - public void Debug(string format, params object[] args) - { - if (args.Length == 0) - { - _log.Debug(format); - } - else - { - _log.Debug(format, args); - } - } - - public void Debug(Exception ex, string format, params object[] args) - { - if (args.Length == 0) - { - _log.Debug(ex, format); - } - else - { - _log.Debug(ex, format, args); - } - } -} diff --git a/src/EventStore.TestClient/ClientApiTcpCommands/WriteFloodProcessor.cs b/src/EventStore.TestClient/ClientApiTcpCommands/WriteFloodProcessor.cs deleted file mode 100644 index 4c2951aa5d..0000000000 --- a/src/EventStore.TestClient/ClientApiTcpCommands/WriteFloodProcessor.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Exceptions; -using EventStore.TestClient.Commands; -using EventStore.TestClient.Statistics; -using EventData = EventStore.ClientAPI.EventData; -using ExpectedVersion = EventStore.Core.Data.ExpectedVersion; -using StreamDeletedException = EventStore.ClientAPI.Exceptions.StreamDeletedException; -using WrongExpectedVersionException = EventStore.ClientAPI.Exceptions.WrongExpectedVersionException; - -namespace EventStore.TestClient.ClientApiTcpCommands; - -internal class WriteFloodProcessor : ICmdProcessor -{ - public string Usage - { - // 0 1 2 3 4 5 - get { return "WRFLTCP [ [ [ [ []]]]]"; } - } - - public string Keyword - { - get { return "WRFLTCP"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 5000; - int streamsCnt = 1000; - int size = 256; - int batchSize = 1; - string streamPrefix = ""; - if (args.Length > 0) - { - if (args.Length < 2 || args.Length > 6) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseLong(args[1]); - if (args.Length >= 3) - { - streamsCnt = MetricPrefixValue.ParseInt(args[2]); - } - - if (args.Length >= 4) - { - size = MetricPrefixValue.ParseInt(args[3]); - } - - if (args.Length >= 5) - { - batchSize = MetricPrefixValue.ParseInt(args[4]); - } - - if (args.Length >= 6) - { - streamPrefix = args[5]; - } - } - catch - { - return false; - } - } - - var stats = new WriteFloodStats(Keyword, context.OutputCsv, args); - var monitor = new RequestMonitor(); - WriteFlood(context, stats, clientsCnt, requestsCnt, streamsCnt, size, batchSize, streamPrefix, monitor) - .GetAwaiter().GetResult(); - return true; - } - - private async Task WriteFlood(CommandProcessorContext context, WriteFloodStats stats, int clientsCnt, long requestsCnt, int streamsCnt, - int size, int batchSize, string streamPrefix, RequestMonitor monitor) - { - context.IsAsync(); - - var doneEvent = new ManualResetEventSlim(false); - var clients = new List(); - - long last = 0; - - var streams = Enumerable.Range(0, streamsCnt).Select(x => - string.IsNullOrWhiteSpace(streamPrefix) - ? Guid.NewGuid().ToString() - : $"{streamPrefix}-{x}").ToArray(); - - context.Log.Information("Writing streams randomly between {first} and {last}", - streams.FirstOrDefault(), - streams.LastOrDefault()); - - var start = new TaskCompletionSource(); - stats.StartTime = DateTime.UtcNow; - var sw2 = new Stopwatch(); - var capacity = 2000 / clientsCnt; - var clientTasks = new List(); - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - - var client = context._clientApiTestClient.CreateConnection(); - await client.ConnectAsync(); - clientTasks.Add(RunClient(client, count)); - } - - async Task RunClient(IEventStoreConnection client, long count) - { - var rnd = new Random(); - List pending = new List(capacity); - await start.Task; - - for (int j = 0; j < count; ++j) - { - var events = new EventData[batchSize]; - for (int q = 0; q < batchSize; q++) - { - events[q] = new EventData(Guid.NewGuid(), - "TakeSomeSpaceEvent", false, - Common.Utils.Helper.UTF8NoBom.GetBytes( - "{ \"DATA\" : \"" + new string('*', size) + "\"}"), - Common.Utils.Helper.UTF8NoBom.GetBytes( - "{ \"METADATA\" : \"" + new string('$', 100) + "\"}")); - } - - var corrid = Guid.NewGuid(); - monitor.StartOperation(corrid); - - pending.Add(client.AppendToStreamAsync(streams[rnd.Next(streamsCnt)], ExpectedVersion.Any, events) - .ContinueWith(t => - { - monitor.EndOperation(corrid); - if (t.IsCompletedSuccessfully) - { - Interlocked.Add(ref stats.Succ, batchSize); - } - else - { - if (Interlocked.Increment(ref stats.Fail) % 1000 == 0) - { - Console.Write("#"); - } - - if (t.Exception != null) - { - var exception = t.Exception.Flatten(); - switch (exception.InnerException) - { - case WrongExpectedVersionException _: - Interlocked.Increment(ref stats.WrongExpVersion); - break; - case StreamDeletedException _: - Interlocked.Increment(ref stats.StreamDeleted); - break; - case OperationTimedOutException _: - Interlocked.Increment(ref stats.CommitTimeout); - break; - } - } - } - - Interlocked.Add(ref stats.Succ, batchSize); - if (stats.Succ - last > 1000) - { - last = stats.Succ; - Console.Write("."); - } - - var localAll = Interlocked.Add(ref stats.All, batchSize); - if (localAll % 100000 == 0) - { - stats.Elapsed = sw2.Elapsed; - stats.Rate = 1000.0 * 100000 / stats.Elapsed.TotalMilliseconds; - sw2.Restart(); - context.Log.Debug( - "\nDONE TOTAL {writes} WRITES IN {elapsed} ({rate:0.0}/s) [S:{success}, F:{failures} (WEV:{wrongExpectedVersion}, P:{prepareTimeout}, C:{commitTimeout}, F:{forwardTimeout}, D:{streamDeleted})].", - localAll, stats.Elapsed, stats.Rate, - stats.Succ, stats.Fail, - stats.WrongExpVersion, stats.PrepTimeout, stats.CommitTimeout, stats.ForwardTimeout, stats.StreamDeleted); - stats.WriteStatsToFile(context.StatsLogger); - } - - if (localAll >= requestsCnt) - { - context.Success(); - doneEvent.Set(); - } - })); - if (pending.Count == capacity) - { - await Task.WhenAny(pending); - - while (pending.Count > 0 && Task.WhenAny(pending).IsCompleted) - { - pending.RemoveAll(x => x.IsCompleted); - if (stats.Succ - last > 1000) - { - Console.Write("."); - last = stats.Succ; - } - } - } - } - - if (pending.Count > 0) - { - await Task.WhenAll(pending); - } - } - - var sw = Stopwatch.StartNew(); - sw2.Start(); - start.SetResult(); - await Task.WhenAll(clientTasks); - sw.Stop(); - - clients.ForEach(client => client.Close()); - - context.Log.Information( - "Completed. Successes: {success}, failures: {failures} (WRONG VERSION: {wrongExpectedVersion}, P: {prepareTimeout}, C: {commitTimeout}, F: {forwardTimeout}, D: {streamDeleted})", - stats.Succ, stats.Fail, - stats.WrongExpVersion, stats.PrepTimeout, stats.CommitTimeout, stats.ForwardTimeout, stats.StreamDeleted); - stats.WriteStatsToFile(context.StatsLogger); - - var reqPerSec = (stats.All + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", stats.All, - sw.ElapsedMilliseconds, reqPerSec); - - PerfUtils.LogData( - Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("successes", stats.Succ), PerfUtils.Col("failures", stats.Fail))); - - var failuresRate = (int)(100 * stats.Fail / (stats.Fail + stats.Succ)); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-{1}-{2}-failureSuccessRate", Keyword, clientsCnt, requestsCnt), failuresRate); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-c{1}-r{2}-st{3}-s{4}-reqPerSec", Keyword, clientsCnt, requestsCnt, streamsCnt, - size), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-c{1}-r{2}-st{3}-s{4}-failureSuccessRate", Keyword, clientsCnt, requestsCnt, - streamsCnt, size), failuresRate); - monitor.GetMeasurementDetails(); - if (Interlocked.Read(ref stats.Succ) != requestsCnt) - { - context.Fail(reason: "There were errors or not all requests completed."); - } - else - { - context.Success(); - } - } -} diff --git a/src/EventStore.TestClient/ClientApiTcpTestClient.cs b/src/EventStore.TestClient/ClientApiTcpTestClient.cs deleted file mode 100644 index ec3781b506..0000000000 --- a/src/EventStore.TestClient/ClientApiTcpTestClient.cs +++ /dev/null @@ -1,44 +0,0 @@ -using EventStore.ClientAPI; -using ILogger = Serilog.ILogger; - -namespace EventStore.TestClient; - -/// -/// A test client that connects using the legacy dotnet TCP client -/// -public class ClientApiTcpTestClient -{ - /// - /// The options specified when starting the EventStore.TestClient - /// - public ClientOptions Options { get; set; } - private ILogger _log; - - /// - /// Constructs a new - /// - /// - /// - public ClientApiTcpTestClient(ClientOptions options, ILogger log) - { - Options = options; - _log = log; - } - - /// - /// Creates a new TCP connection. - /// - /// - public IEventStoreConnection CreateConnection() - { - var connectionString = string.IsNullOrWhiteSpace(Options.ConnectionString) - ? $"ConnectTo=tcp://{Options.Host}:{Options.TcpPort};UseSslConnection={Options.UseTls};ValidateServer={Options.TlsValidateServer}" - : Options.ConnectionString; - _log.Debug("Creating TCP client with connection string '{connectionString}.", connectionString); - - var connectionSettings = ConnectionSettings.Create() - .LimitRetriesForOperationTo(0) - .KeepReconnecting(); - return EventStoreConnection.Create(connectionString, connectionSettings); - } -} diff --git a/src/EventStore.TestClient/ClientOptions.cs b/src/EventStore.TestClient/ClientOptions.cs index 88e7b51c7b..e92b6cfdcb 100644 --- a/src/EventStore.TestClient/ClientOptions.cs +++ b/src/EventStore.TestClient/ClientOptions.cs @@ -17,14 +17,12 @@ namespace EventStore.TestClient; public sealed record ClientOptions { public string Host { get; init; } - public int TcpPort { get; init; } public int HttpPort { get; init; } public int Timeout { get; init; } public int ReadWindow { get; init; } public int WriteWindow { get; init; } public int PingWindow { get; init; } public string[] Command { get; init; } - public bool Reconnect { get; set; } public bool UseTls { get; init; } public bool TlsValidateServer { get; init; } @@ -37,13 +35,11 @@ public ClientOptions() { Command = Array.Empty(); Host = IPAddress.Loopback.ToString(); - TcpPort = 1113; HttpPort = 2113; Timeout = -1; ReadWindow = 2000; WriteWindow = 2000; PingWindow = 2000; - Reconnect = true; UseTls = false; TlsValidateServer = false; ConnectionString = string.Empty; @@ -58,8 +54,12 @@ public override string ToString() (builder, option) => builder.AppendLine($"{option.Name}: {GetValue(option)}")) .ToString(); - object GetValue(PropertyInfo propertyInfo) => propertyInfo.PropertyType.IsArray - ? string.Join(",", ((IEnumerable)propertyInfo.GetValue(this)).OfType()) - : propertyInfo.GetValue(this); + object GetValue(PropertyInfo propertyInfo) => propertyInfo.Name switch + { + nameof(ConnectionString) when !string.IsNullOrEmpty(ConnectionString) => "[REDACTED]", + _ when propertyInfo.PropertyType.IsArray => + string.Join(",", ((IEnumerable)propertyInfo.GetValue(this)).OfType()), + _ => propertyInfo.GetValue(this) + }; } } diff --git a/src/EventStore.TestClient/CommandProcessorContext.cs b/src/EventStore.TestClient/CommandProcessorContext.cs index fdb720428a..a7db6df513 100644 --- a/src/EventStore.TestClient/CommandProcessorContext.cs +++ b/src/EventStore.TestClient/CommandProcessorContext.cs @@ -31,23 +31,20 @@ public class CommandProcessorContext /// public bool OutputCsv = false; - public readonly TcpTestClient _tcpTestClient; public readonly GrpcTestClient _grpcTestClient; - public readonly ClientApiTcpTestClient _clientApiTestClient; private readonly ManualResetEventSlim _doneEvent; private readonly CancellationToken _cancellationToken; private int _completed; private int _timeout; - public CommandProcessorContext(TcpTestClient tcpTestClient, GrpcTestClient grpcTestClient, - ClientApiTcpTestClient clientApiTestClient, int timeout, ILogger log, ILogger statsLogger, bool outputCsv, + public CancellationToken CancellationToken => _cancellationToken; + + public CommandProcessorContext(GrpcTestClient grpcTestClient, + int timeout, ILogger log, ILogger statsLogger, bool outputCsv, ManualResetEventSlim doneEvent, CancellationToken cancellationToken) { - - _tcpTestClient = tcpTestClient; _grpcTestClient = grpcTestClient; - _clientApiTestClient = clientApiTestClient; Log = log; StatsLogger = statsLogger; _doneEvent = doneEvent; diff --git a/src/EventStore.TestClient/Commands/DeleteProcessor.cs b/src/EventStore.TestClient/Commands/DeleteProcessor.cs deleted file mode 100644 index 7ec096e2a1..0000000000 --- a/src/EventStore.TestClient/Commands/DeleteProcessor.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Diagnostics; -using EventStore.Client.Messages; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; -using OperationResult = EventStore.Client.Messages.OperationResult; - -namespace EventStore.TestClient.Commands; - -internal class DeleteProcessor : ICmdProcessor -{ - public string Usage - { - get { return "DEL [ []]"; } - } - - public string Keyword - { - get { return "DEL"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - var eventStreamId = "test-stream"; - var expectedVersion = ExpectedVersion.Any; - - if (args.Length > 0) - { - if (args.Length > 2) - { - return false; - } - - eventStreamId = args[0]; - if (args.Length == 2) - { - expectedVersion = args[1].Trim().ToUpper() == "ANY" ? ExpectedVersion.Any : int.Parse(args[1]); - } - } - - context.IsAsync(); - var sw = new Stopwatch(); - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information( - "[{remoteEndPoint}, L{localEndPoint}]: Trying to delete event stream '{stream}'...", - conn.RemoteEndPoint, conn.LocalEndPoint, eventStreamId); - var corrid = Guid.NewGuid(); - var deleteDto = new DeleteStream(eventStreamId, expectedVersion, false, true); - var package = new TcpPackage(TcpCommand.DeleteStream, corrid, deleteDto.Serialize()).AsByteArray(); - sw.Start(); - conn.EnqueueSend(package); - }, - handlePackage: (conn, pkg) => - { - sw.Stop(); - context.Log.Information("Delete request took: {elapsed}.", sw.Elapsed); - - if (pkg.Command != TcpCommand.DeleteStreamCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result == OperationResult.Success) - { - context.Log.Information("DELETED event stream {stream}.", eventStreamId); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds)); - context.Success(); - } - else - { - context.Log.Information("DELETION FAILED for event stream {stream}: {message} ({e}).", eventStreamId, - dto.Message, dto.Result); - context.Fail(); - } - - conn.Close(); - }, - connectionClosed: (connection, error) => context.Fail(reason: "Connection was closed prematurely.")); - - context.WaitForCompletion(); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs b/src/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs deleted file mode 100644 index c2c56853d4..0000000000 --- a/src/EventStore.TestClient/Commands/DvuBasic/BankAccountBasicProducer.cs +++ /dev/null @@ -1,292 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using EventStore.Common.Utils; -using EventStore.Core.Data; -using EventStore.TestClient.Commands; -using ILogger = Serilog.ILogger; -#pragma warning disable 1591 - -namespace EventStore.TestClient.Commands.DvuBasic; - -internal class BankAccountBasicProducer : IBasicProducer -{ - private static readonly ILogger Log = Serilog.Log.ForContext(); - - public string Name - { - get { return "account"; } - } - - public Event Create(int version) - { - var accountObject = BankAccountEventFactory.CreateAccountObject(version); - - var serializedObject = TestClientJson.To(accountObject); - var @event = new Event(Guid.NewGuid(), accountObject.GetType().Name, true, - Helper.UTF8NoBom.GetBytes(serializedObject), new byte[0]); - - return @event; - } - - public bool Equal(int eventVersion, string eventType, byte[] actualData) - { - var generated = BankAccountEventFactory.CreateAccountObject(eventVersion); - object deserialized; - - bool isEqual; - string reason; - if (actualData == null) - { - isEqual = false; - reason = "Null received"; - } - else - { - deserialized = Deserialize(eventType, actualData); - - if (deserialized.GetType() != generated.GetType()) - { - isEqual = false; - reason = string.Format("Type does not match, actual type is {0}", deserialized.GetType().Name); - } - else - { - isEqual = generated.Equals(deserialized); - reason = "Value differs"; - } - } - - if (!isEqual) - { - LogExpected(generated, actualData, reason); - } - - return isEqual; - } - - private object Deserialize(string eventType, byte[] actualData) - { - object result; - var strData = Helper.UTF8NoBom.GetString(actualData); - if (eventType == typeof(AccountCreated).FullName) - { - result = TestClientJson.From(strData); - } - else - { - if (eventType == typeof(AccountCredited).Name) - { - result = TestClientJson.From(strData); - } - else - { - if (eventType == typeof(AccountDebited).Name) - { - result = TestClientJson.From(strData); - } - else - { - if (eventType == typeof(AccountCheckPoint).Name) - { - result = TestClientJson.From(strData); - } - else - { - throw new NotSupportedException(string.Format("Event type {0} is not recognized.", - eventType)); - } - } - } - } - - return result; - } - - private static void LogExpected(object generated, object actual, string reason) - { - Log.Information("Expected: {expected}\n" + - " Actual: {actual}\n" + - " Details: {reason}", - generated.ToString(), - (actual == null ? "" : actual.ToString()), - reason - ); - } -} - -public class AccountCreated -{ - public readonly string AccountNumber; - - public AccountCreated(string accountNumber) - { - AccountNumber = accountNumber; - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - var casted = (AccountCreated)obj; - return Equals(casted); - } - - protected bool Equals(AccountCreated other) - { - return string.Equals(AccountNumber, other.AccountNumber); - } - - public override int GetHashCode() - { - return (AccountNumber != null ? AccountNumber.GetHashCode() : 0); - } - - public override string ToString() - { - return string.Format("Type: {0}; AccountNumber: {1}", GetType().Name, AccountNumber); - } -} - -public class AccountCredited -{ - public readonly decimal CreditedAmount; - public readonly string Kind; - - public AccountCredited(decimal creditedAmount, int kind) - { - CreditedAmount = creditedAmount; - Kind = kind.ToString(CultureInfo.InvariantCulture); - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - var casted = (AccountCredited)obj; - return Equals(casted); - } - - protected bool Equals(AccountCredited other) - { - return CreditedAmount == other.CreditedAmount; - } - - public override int GetHashCode() - { - return CreditedAmount.GetHashCode(); - } - - public override string ToString() - { - return string.Format("Type: {0}; CreditedAmount: {1}", GetType().Name, CreditedAmount); - } -} - -public class AccountDebited -{ - public readonly decimal DebitedAmount; - public readonly string Kind; - - public AccountDebited(decimal debitedAmount, int kind) - { - DebitedAmount = debitedAmount; - Kind = kind.ToString(CultureInfo.InvariantCulture); - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - var casted = (AccountDebited)obj; - return Equals(casted); - } - - protected bool Equals(AccountDebited other) - { - return DebitedAmount == other.DebitedAmount; - } - - public override int GetHashCode() - { - return DebitedAmount.GetHashCode(); - } - - public override string ToString() - { - return string.Format("Type: {0}; DebitedAmount: {1}", GetType().Name, DebitedAmount); - } -} - -public class AccountCheckPoint -{ - public readonly decimal CreditedAmount; - public readonly decimal DebitedAmount; - - public AccountCheckPoint(decimal creditedAmount, decimal debitedAmount) - { - CreditedAmount = creditedAmount; - DebitedAmount = debitedAmount; - } - - public override bool Equals(object obj) - { - if (obj == null) - { - return false; - } - - if (obj.GetType() != GetType()) - { - return false; - } - - var casted = (AccountCheckPoint)obj; - return Equals(casted); - } - - protected bool Equals(AccountCheckPoint other) - { - return CreditedAmount == other.CreditedAmount && DebitedAmount == other.DebitedAmount; - } - - public override int GetHashCode() - { - unchecked - { - return (CreditedAmount.GetHashCode() * 397) ^ DebitedAmount.GetHashCode(); - } - } - - public override string ToString() - { - return string.Format("Type: {0}; CreditedAmount: {1}, DebitedAmount: {2}", - GetType().Name, - CreditedAmount, - DebitedAmount); - } -} diff --git a/src/EventStore.TestClient/Commands/DvuBasic/BankAccountEventFactory.cs b/src/EventStore.TestClient/Commands/DvuBasic/BankAccountEventFactory.cs deleted file mode 100644 index 81f7bad717..0000000000 --- a/src/EventStore.TestClient/Commands/DvuBasic/BankAccountEventFactory.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace EventStore.TestClient.Commands.DvuBasic; - -internal static class BankAccountEventFactory -{ - public static object CreateAccountObject(int version) - { - object accountObject; - - var internalCounter = version + 1; - - { - const int checkpointVersion = 10; - - var checkPointModVersion = internalCounter % checkpointVersion; - if (checkPointModVersion == 0) - { - int otherCheckPointsCount = internalCounter / checkpointVersion; - - var elementsCount = internalCounter / 2; - - var creditedSum = ComputeSum(20, elementsCount, 20) - ComputeSum(100, otherCheckPointsCount, 100); - var debitedSum = ComputeSum(10, elementsCount, 20); - - var checkpoint = new AccountCheckPoint(creditedSum, debitedSum); - - accountObject = checkpoint; - } - else - { - var modVersion = internalCounter % 2; - if (modVersion == 0) - { - var credited = new AccountCredited(internalCounter * 10, internalCounter % 17); - accountObject = credited; - } - else - { - var debited = new AccountDebited(internalCounter * 10, internalCounter % 17); - accountObject = debited; - } - } - } - return accountObject; - } - - private static int ComputeSum(int first, int count, int step) - { - var sum = count * (2 * first + step * (count - 1)) / 2; - return sum; - } -} diff --git a/src/EventStore.TestClient/Commands/DvuBasic/DvuBasicProcessor.cs b/src/EventStore.TestClient/Commands/DvuBasic/DvuBasicProcessor.cs deleted file mode 100644 index d84b762db8..0000000000 --- a/src/EventStore.TestClient/Commands/DvuBasic/DvuBasicProcessor.cs +++ /dev/null @@ -1,491 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Threading; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Data; -using EventStore.Core.Messages; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; -using OperationResult = EventStore.Client.Messages.OperationResult; - -namespace EventStore.TestClient.Commands.DvuBasic; - -internal class DvuBasicProcessor : ICmdProcessor -{ - public string Keyword - { - get { return "verify"; } - } - - public string Usage - { - get - { - return string.Format("{0} " + - " " + - " " + - " " + - " " + - "", - Keyword, - string.Join(",", AvailableProducers)); - } - } - - public IEnumerable AvailableProducers - { - get { yield return "bank"; } - } - - public IBasicProducer[] Producers { get; set; } - - private int _writers; - private string[] _streams; - private int[] _heads; - - private volatile bool _stopReading; - private readonly object _factoryLock = new object(); - - public bool Execute(CommandProcessorContext context, string[] args) - { - var writers = 20; - var readers = 30; - var events = 1000000; - var streams = 1000; - var producers = new[] { "bank" }; - - if (args.Length != 0 && args.Length != 5) - { - context.Log.Error("Invalid number of arguments. Should be 0 or 5"); - return false; - } - - if (args.Length > 0) - { - int writersArg; - int readersArg; - int eventsArg; - int streamsArg; - - if (!int.TryParse(args[0], out writersArg)) - { - context.Log.Error("Invalid argument value for "); - return false; - } - - if (!int.TryParse(args[1], out readersArg)) - { - context.Log.Error("Invalid argument value for "); - return false; - } - - if (!int.TryParse(args[2], out eventsArg)) - { - context.Log.Error("Invalid argument value for "); - return false; - } - - if (!int.TryParse(args[3], out streamsArg)) - { - context.Log.Error("Invalid argument value for "); - return false; - } - - string[] producersArg = args[4].Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) - .Select(p => p.Trim().ToLower()).Distinct().ToArray(); - if (producersArg.Length <= 0) - { - context.Log.Error("Invalid argument value for "); - return false; - } - - if (producersArg.Any(p => !AvailableProducers.Contains(p))) - { - context.Log.Error("Invalid producers argument. Pass comma-separated subset of [{producers}]", - string.Join(",", AvailableProducers)); - return false; - } - - writers = writersArg; - readers = readersArg; - events = eventsArg; - streams = streamsArg; - producers = producersArg; - } - - _writers = writers; - return InitProducers(producers) && Run(context, writers, readers, events, streams); - } - - private bool InitProducers(string[] producers) - { - if (producers.Length == 1 && producers[0] == "bank") - { - Producers = new IBasicProducer[] { new BankAccountBasicProducer() }; - return true; - } - - return false; - } - - private bool Run(CommandProcessorContext context, int writers, int readers, int events, int streams) - { - context.IsAsync(); - - _streams = new string[streams * Producers.Length]; - for (var i = 0; i < Producers.Length; i++) - { - for (var j = i * streams; j < streams * (i + 1); j++) - { - _streams[j] = StreamNamesGenerator.GenerateName(Producers[i].Name, j - i * streams); - } - } - - _heads = Enumerable.Repeat(-1, streams * Producers.Length).ToArray(); - - return Verify(context, writers, readers, events); - } - - private bool Verify(CommandProcessorContext context, int writers, int readers, int events) - { - var readStatuses = Enumerable.Range(0, readers).Select(x => new Status(context.Log)).ToArray(); - var readNotifications = Enumerable.Range(0, readers).Select(x => new ManualResetEventSlim(false)).ToArray(); - var writeStatuses = Enumerable.Range(0, writers).Select(x => new Status(context.Log)).ToArray(); - var writeNotifications = - Enumerable.Range(0, writers).Select(x => new ManualResetEventSlim(false)).ToArray(); - - for (int i = 0; i < readers; i++) - { - var i1 = i; - var thread = new Thread(() => Read(readStatuses[i1], i1, context, readNotifications[i1])); - thread.IsBackground = true; - thread.Start(); - } - - for (int i = 0; i < writers; i++) - { - var i1 = i; - var thread = new Thread(() => - Write(writeStatuses[i1], i1, context, events / writers, writeNotifications[i1])); - thread.IsBackground = true; - thread.Start(); - } - - foreach (var writeNotification in writeNotifications) - { - writeNotification.Wait(); - } - - _stopReading = true; - foreach (var readNotification in readNotifications) - { - readNotification.Wait(); - } - - context.Log.Information("dvub finished execution : "); - - var writersTable = new ConsoleTable("WRITER ID", "Status"); - - foreach (var ws in writeStatuses) - { - writersTable.AppendRow(ws.ThreadId.ToString(), ws.Success ? "Success" : "Fail"); - } - - var readersTable = new ConsoleTable("READER ID", "Status"); - foreach (var rs in readStatuses) - { - readersTable.AppendRow(rs.ThreadId.ToString(), rs.Success ? "Success" : "Fail"); - } - - context.Log.Information(writersTable.CreateIndentedTable()); - context.Log.Information(readersTable.CreateIndentedTable()); - - var success = writeStatuses.All(s => s.Success) && readStatuses.All(s => s.Success); - if (success) - { - context.Success(); - } - else - { - context.Fail(); - } - - return success; - } - - private void Write(Status status, int writerIdx, CommandProcessorContext context, int requests, - ManualResetEventSlim finish) - { - TcpTypedConnection connection; - var iteration = new AutoResetEvent(false); - - var sent = 0; - - var prepareTimeouts = 0; - var commitTimeouts = 0; - var forwardTimeouts = 0; - var wrongExpectedVersion = 0; - var streamsDeleted = 0; - - var failed = 0; - - var rnd = new Random(writerIdx); - - var streamIdx = -1; - var head = -1; - - Action, TcpPackage> packageHandler = (conn, pkg) => - { - var dto = pkg.Data.Deserialize(); - switch (dto.Result) - { - case OperationResult.Success: - lock (_heads) - { - var currentHead = _heads[streamIdx]; - Ensure.Equal(currentHead, head, "currentHead"); - _heads[streamIdx]++; - } - - break; - case OperationResult.PrepareTimeout: - prepareTimeouts++; - failed++; - break; - case OperationResult.CommitTimeout: - commitTimeouts++; - failed++; - break; - case OperationResult.ForwardTimeout: - forwardTimeouts++; - failed++; - break; - case OperationResult.WrongExpectedVersion: - wrongExpectedVersion++; - failed++; - break; - case OperationResult.StreamDeleted: - streamsDeleted++; - failed++; - break; - default: - throw new ArgumentOutOfRangeException(); - } - - sent++; - if (sent % 1000 == 0) - { - status.ReportWritesProgress(writerIdx, sent, prepareTimeouts, commitTimeouts, forwardTimeouts, - wrongExpectedVersion, streamsDeleted, failed, requests); - } - - iteration.Set(); - }; - - Action> established = _ => { }; - Action, SocketError> closed = null; - closed = (_, __) => - { - if (!context._tcpTestClient.Options.Reconnect) - { - return; - } - - Thread.Sleep(TimeSpan.FromSeconds(1)); - connection = - context._tcpTestClient.CreateTcpConnection(context, packageHandler, cn => iteration.Set(), closed, false); - }; - - connection = context._tcpTestClient.CreateTcpConnection(context, packageHandler, established, closed, false); - - for (var i = 0; i < requests; ++i) - { - streamIdx = NextStreamForWriting(rnd, writerIdx); - lock (_heads) - { - head = _heads[streamIdx]; - } - - var evnt = CreateEvent(_streams[streamIdx], head + 1); - var write = new WriteEvents( - _streams[streamIdx], - head, - new[] { - new NewEvent(evnt.EventId.ToByteArray(), evnt.EventType, - evnt.IsJson ? 1 : 0, 0, evnt.Data, evnt.Metadata) - }, - false); - - var package = new TcpPackage(TcpCommand.WriteEvents, Guid.NewGuid(), write.Serialize()); - connection.EnqueueSend(package.AsByteArray()); - iteration.WaitOne(); - } - - status.ReportWritesProgress(writerIdx, sent, prepareTimeouts, commitTimeouts, forwardTimeouts, - wrongExpectedVersion, streamsDeleted, failed, requests); - status.FinalizeStatus(writerIdx, failed != sent); - context._tcpTestClient.Options.Reconnect = false; - connection.Close(); - finish.Set(); - } - - private void Read(Status status, int readerIdx, CommandProcessorContext context, - ManualResetEventSlim finishedEvent) - { - TcpTypedConnection connection; - var iteration = new AutoResetEvent(false); - - var successes = 0; - var fails = 0; - - var rnd = new Random(readerIdx); - - var streamIdx = -1; - var eventidx = -1; - - Action, TcpPackage> packageReceived = (conn, pkg) => - { - var dto = pkg.Data.Deserialize(); - switch (dto.Result) - { - case ReadEventCompleted.Types.ReadEventResult.Success: - if (Equal(_streams[streamIdx], eventidx, dto.Event.Event.EventType, dto.Event.Event.Data.ToByteArray())) - { - successes++; - if (successes % 1000 == 0) - { - status.ReportReadsProgress(readerIdx, successes, fails); - } - } - else - { - fails++; - status.ReportReadError(readerIdx, _streams[streamIdx], eventidx); - } - - break; - case ReadEventCompleted.Types.ReadEventResult.NotFound: - case ReadEventCompleted.Types.ReadEventResult.NoStream: - case ReadEventCompleted.Types.ReadEventResult.StreamDeleted: - case ReadEventCompleted.Types.ReadEventResult.Error: - case ReadEventCompleted.Types.ReadEventResult.AccessDenied: - fails++; - status.ReportNotFoundOnRead(readerIdx, _streams[streamIdx], eventidx); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - iteration.Set(); - }; - Action> established = _ => { }; - Action, SocketError> closed = null; - closed = (_, __) => - { - if (!context._tcpTestClient.Options.Reconnect) - { - return; - } - - Thread.Sleep(TimeSpan.FromSeconds(1)); - connection = - context._tcpTestClient.CreateTcpConnection(context, packageReceived, cn => iteration.Set(), closed, false); - }; - - connection = context._tcpTestClient.CreateTcpConnection(context, packageReceived, established, closed, false); - - while (!_stopReading) - { - streamIdx = NextStreamForReading(rnd, readerIdx); - int head; - lock (_heads) - { - head = _heads[streamIdx]; - } - - if (head > 0) - { - eventidx = NextRandomEventVersion(rnd, head); - var stream = _streams[streamIdx]; - var corrid = Guid.NewGuid(); - var read = new ReadEvent(stream, eventidx, resolveLinkTos: false, - requireLeader: false); - var package = new TcpPackage(TcpCommand.ReadEvent, corrid, read.Serialize()); - - connection.EnqueueSend(package.AsByteArray()); - iteration.WaitOne(); - } - else - { - Thread.Sleep(100); - } - } - - status.ReportReadsProgress(readerIdx, successes, fails); - status.FinalizeStatus(readerIdx, fails == 0); - context._tcpTestClient.Options.Reconnect = false; - connection.Close(); - finishedEvent.Set(); - } - - private int NextStreamForWriting(Random rnd, int writerIdx) - { - if (_writers >= _streams.Length) - { - if (_writers > _streams.Length) - { - return writerIdx % _streams.Length; - } - - return writerIdx; - } - - return rnd.Next(_streams.Length); - } - - private int NextStreamForReading(Random rnd, int readerIdx) - { - return rnd.Next(_streams.Length); - } - - private int NextRandomEventVersion(Random rnd, int head) - { - return head % 2 == 0 ? head : rnd.Next(1, head); - } - - private IBasicProducer CorrespondingProducer(string stream) - { - return Producers.Single(f => string.Equals(f.Name, stream, StringComparison.OrdinalIgnoreCase)); - } - - private Event CreateEvent(string stream, int version) - { - var originalName = StreamNamesGenerator.GetOriginalName(stream); - var factory = CorrespondingProducer(originalName); - - Event generated; - lock (_factoryLock) - { - generated = factory.Create(version); - } - - return generated; - } - - private bool Equal(string stream, int expectedIdx, string eventType, byte[] actual) - { - var originalName = StreamNamesGenerator.GetOriginalName(stream); - var producer = CorrespondingProducer(originalName); - - bool equal; - lock (_factoryLock) - { - equal = producer.Equal(expectedIdx, eventType, actual); - } - - return equal; - } -} diff --git a/src/EventStore.TestClient/Commands/DvuBasic/IBasicProducer.cs b/src/EventStore.TestClient/Commands/DvuBasic/IBasicProducer.cs deleted file mode 100644 index 3384d87a5a..0000000000 --- a/src/EventStore.TestClient/Commands/DvuBasic/IBasicProducer.cs +++ /dev/null @@ -1,12 +0,0 @@ -using EventStore.Core.Data; -#pragma warning disable 1591 - -namespace EventStore.TestClient.Commands.DvuBasic; - -public interface IBasicProducer -{ - string Name { get; } - - Event Create(int version); - bool Equal(int eventVersion, string eventType, byte[] actualData); -} diff --git a/src/EventStore.TestClient/Commands/DvuBasic/Status.cs b/src/EventStore.TestClient/Commands/DvuBasic/Status.cs deleted file mode 100644 index d00c58c398..0000000000 --- a/src/EventStore.TestClient/Commands/DvuBasic/Status.cs +++ /dev/null @@ -1,95 +0,0 @@ -using EventStore.Common.Utils; -using ILogger = Serilog.ILogger; -#pragma warning disable 1591 - -namespace EventStore.TestClient.Commands.DvuBasic; - -public class Status -{ - private readonly Serilog.ILogger _log; - - public int ThreadId { get; private set; } - public bool Success { get; private set; } - - public Status(ILogger log) - { - Ensure.NotNull(log, "log"); - _log = log; - } - - public void ReportWritesProgress(int threadId, - int sent, - int prepareTimeouts, - int commitTimeouts, - int forwardTimeouts, - int wrongExpctdVersions, - int streamsDeleted, - int fails, - int requests) - { - var sentP = ToPercent(sent, requests); - var failsP = ToPercent(fails, sent); - - var table = new ConsoleTable("WRITER ID", "Completed %", "Completed/Total", - "Failed %", "Failed/Sent", "Prepare Timeouts", - "Commit Timeouts", "Forward Timeouts", - "Wrong Versions", "Stream Deleted"); - table.AppendRow(threadId.ToString(), string.Format("{0:0.0}%", sentP), - string.Format("{0}/{1}", sent, requests), - string.Format("{0:0.0}%", failsP), string.Format("{0}/{1}", fails, sent), prepareTimeouts.ToString(), - commitTimeouts.ToString(), forwardTimeouts.ToString(), - wrongExpctdVersions.ToString(), streamsDeleted.ToString()); - - if (failsP > 50d) - { - _log.Fatal(table.CreateIndentedTable()); - } - else - { - _log.Information(table.CreateIndentedTable()); - } - } - - public void ReportReadsProgress(int threadId, int successes, int fails) - { - var all = successes + fails; - - var table = new ConsoleTable("READER ID", "Fails", "Total Random Reads"); - table.AppendRow(threadId.ToString(), fails.ToString(), all.ToString()); - - if (fails != 0) - { - _log.Fatal(table.CreateIndentedTable()); - } - else - { - _log.Information(table.CreateIndentedTable()); - } - } - - public void ReportReadError(int threadId, string stream, int index) - { - _log.Fatal("FATAL : READER [{threadId}] encountered an error in {stream} ({index})", threadId, stream, - index); - } - - public void ReportNotFoundOnRead(int threadId, string stream, int index) - { - _log.Fatal( - "FATAL : READER [{threadId}] asked for event {index} in '{stream}' but server returned 'Not Found'", - threadId, - index, stream); - } - - public void FinalizeStatus(int threadId, bool success) - { - ThreadId = threadId; - Success = success; - } - - private double ToPercent(int value, int max) - { - var approx = (value / (double)max) * 100d; - return approx <= 100d ? approx : 100; - } -} diff --git a/src/EventStore.TestClient/Commands/DvuBasic/StreamNamesGenerator.cs b/src/EventStore.TestClient/Commands/DvuBasic/StreamNamesGenerator.cs deleted file mode 100644 index 9461091880..0000000000 --- a/src/EventStore.TestClient/Commands/DvuBasic/StreamNamesGenerator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace EventStore.TestClient.Commands.DvuBasic; - -internal static class StreamNamesGenerator -{ - public static string GenerateName(string original, int index) - { - return string.Format("{0}-{1}", original, index); - } - - public static string GetOriginalName(string autogenerated) - { - return autogenerated.Substring(0, autogenerated.LastIndexOf('-')); - } -} diff --git a/src/EventStore.TestClient/Commands/MultiWriteFloodWaiting.cs b/src/EventStore.TestClient/Commands/MultiWriteFloodWaiting.cs deleted file mode 100644 index 45f9cc1ba4..0000000000 --- a/src/EventStore.TestClient/Commands/MultiWriteFloodWaiting.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using EventStore.Client.Messages; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; -using OperationResult = EventStore.Client.Messages.OperationResult; - -namespace EventStore.TestClient.Commands; - -internal class MultiWriteFloodWaitingProcessor : ICmdProcessor -{ - public string Usage - { - get { return "MWRFLW [ [ ]]"; } - } - - public string Keyword - { - get { return "MWRFLW"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 5000; - var writeCount = 10; - - if (args.Length > 0) - { - if (args.Length != 1 && args.Length != 3) - { - return false; - } - - try - { - writeCount = MetricPrefixValue.ParseInt(args[0]); - if (args.Length > 1) - { - clientsCnt = MetricPrefixValue.ParseInt(args[1]); - requestsCnt = MetricPrefixValue.ParseLong(args[2]); - } - } - catch - { - return false; - } - } - - WriteFlood(context, writeCount, clientsCnt, requestsCnt); - return true; - } - - private void WriteFlood(CommandProcessorContext context, int writeCnt, int clientsCnt, long requestsCnt) - { - const string data = "test-data"; - - context.IsAsync(); - - var clients = new List>(); - var threads = new List(); - var doneEvent = new ManualResetEventSlim(false); - long succ = 0; - long fail = 0; - long all = 0; - - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - var localDoneEvent = new AutoResetEvent(false); - var eventStreamId = "es" + Guid.NewGuid(); - var client = context._tcpTestClient.CreateTcpConnection( - context, - (conn, pkg) => - { - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result == OperationResult.Success) - { - if (Interlocked.Increment(ref succ) % 1000 == 0) - { - Console.Write("."); - } - } - else - { - if (Interlocked.Increment(ref fail) % 1000 == 0) - { - Console.Write("#"); - } - } - - if (Interlocked.Increment(ref all) == requestsCnt) - { - context.Success(); - doneEvent.Set(); - } - - localDoneEvent.Set(); - }, - connectionClosed: (conn, err) => context.Fail(reason: "Connection was closed prematurely.")); - clients.Add(client); - - threads.Add(new Thread(() => - { - for (int j = 0; j < count; ++j) - { - var writeDto = new WriteEvents( - eventStreamId, - ExpectedVersion.Any, - Enumerable.Range(0, writeCnt).Select(x => - new NewEvent(Guid.NewGuid().ToByteArray(), - "type", - 0, 0, - Common.Utils.Helper.UTF8NoBom.GetBytes(data), - new byte[0])).ToArray(), - false); - var package = new TcpPackage(TcpCommand.WriteEvents, Guid.NewGuid(), writeDto.Serialize()); - client.EnqueueSend(package.AsByteArray()); - localDoneEvent.WaitOne(); - } - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - sw.Stop(); - clients.ForEach(client => client.Close()); - - var reqPerSec = (all + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("Completed. Successes: {success}, failures: {failures}", succ, fail); - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", all, - sw.ElapsedMilliseconds, reqPerSec); - - PerfUtils.LogData(Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("successes", succ), PerfUtils.Col("failures", fail))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-{1}-{2}-failureSuccessRate", Keyword, clientsCnt, requestsCnt), - (int)(100.0 * fail / (fail + succ))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds / requestsCnt)); - - if (succ != requestsCnt) - { - context.Fail(reason: "There were errors or not all requests completed."); - } - else - { - context.Success(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/MultiWriteProcessor.cs b/src/EventStore.TestClient/Commands/MultiWriteProcessor.cs deleted file mode 100644 index 68db128631..0000000000 --- a/src/EventStore.TestClient/Commands/MultiWriteProcessor.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class MultiWriteProcessor : ICmdProcessor -{ - public string Usage - { - get { return "MWR [ [ []]"; } - } - - public string Keyword - { - get { return "MWR"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - const string data = "test-data"; - var eventStreamId = "test-stream"; - var writeCount = 10; - var expectedVersion = ExpectedVersion.Any; - - if (args.Length > 0) - { - if (args.Length > 3) - { - return false; - } - - writeCount = MetricPrefixValue.ParseInt(args[0]); - if (args.Length >= 2) - { - eventStreamId = args[1]; - } - - if (args.Length >= 3) - { - expectedVersion = args[2].Trim().ToUpper() == "ANY" - ? ExpectedVersion.Any - : int.Parse(args[2].Trim()); - } - } - - context.IsAsync(); - var sw = new Stopwatch(); - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information("[{remoteEndPoint}, L{localEndPoint}]: Writing...", conn.RemoteEndPoint, - conn.LocalEndPoint); - var writeDto = new WriteEvents( - eventStreamId, - expectedVersion, - Enumerable.Range(0, writeCount).Select(x => new NewEvent( - Guid.NewGuid().ToByteArray(), - "type", - 0, 0, - Helper.UTF8NoBom.GetBytes(data), - new byte[0])).ToArray(), - false); - var package = new TcpPackage(TcpCommand.WriteEvents, Guid.NewGuid(), writeDto.Serialize()) - .AsByteArray(); - sw.Start(); - conn.EnqueueSend(package); - }, - handlePackage: (conn, pkg) => - { - sw.Stop(); - context.Log.Information("Write request took: {elapsed}.", sw.Elapsed); - - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result == EventStore.Client.Messages.OperationResult.Success) - { - context.Log.Information("Successfully written {writeCount} events.", writeCount); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds)); - context.Success(); - } - else - { - context.Log.Information("Error while writing: {e}.", dto.Result); - context.Fail(); - } - - conn.Close(); - }, - connectionClosed: (connection, error) => context.Fail(reason: "Connection was closed prematurely.")); - - context.WaitForCompletion(); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/PingFloodProcessor.cs b/src/EventStore.TestClient/Commands/PingFloodProcessor.cs deleted file mode 100644 index a480f7f0d7..0000000000 --- a/src/EventStore.TestClient/Commands/PingFloodProcessor.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class PingFloodProcessor : ICmdProcessor -{ - private static readonly byte[] Payload = new byte[0]; - - public string Usage - { - get { return "PINGFL [ ]"; } - } - - public string Keyword - { - get { return "PINGFL"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 1000000; - if (args.Length > 0) - { - if (args.Length != 2) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseLong(args[1]); - } - catch - { - return false; - } - } - - PingFlood(context, clientsCnt, requestsCnt); - return true; - } - - private void PingFlood(CommandProcessorContext context, int clientsCnt, long requestsCnt) - { - context.IsAsync(); - - var doneEvent = new ManualResetEventSlim(false); - var clients = new List>(); - var threads = new List(); - long all = 0; - - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - long received = 0; - long sent = 0; - var client = context._tcpTestClient.CreateTcpConnection( - context, - (conn, msg) => - { - Interlocked.Increment(ref received); - var pongs = Interlocked.Increment(ref all); - if (pongs % 10000 == 0) - { - Console.Write('.'); - } - - if (pongs == requestsCnt) - { - context.Success(); - doneEvent.Set(); - } - }, - connectionClosed: (conn, err) => context.Fail(reason: "Connection was closed prematurely.")); - clients.Add(client); - - threads.Add(new Thread(() => - { - for (int j = 0; j < count; ++j) - { - var package = new TcpPackage(TcpCommand.Ping, Guid.NewGuid(), Payload); - client.EnqueueSend(package.AsByteArray()); - - var localSent = Interlocked.Increment(ref sent); - while (localSent - Interlocked.Read(ref received) > - context._tcpTestClient.Options.PingWindow / clientsCnt) - { - Thread.Sleep(1); - } - } - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - sw.Stop(); - clients.ForEach(client => client.Close()); - - var reqPerSec = (all + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", all, - sw.ElapsedMilliseconds, reqPerSec); - PerfUtils.LogData(Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - - if (Interlocked.Read(ref all) == requestsCnt) - { - context.Success(); - } - else - { - context.Fail(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/PingFloodWaitingProcessor.cs b/src/EventStore.TestClient/Commands/PingFloodWaitingProcessor.cs deleted file mode 100644 index 38305b3052..0000000000 --- a/src/EventStore.TestClient/Commands/PingFloodWaitingProcessor.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class PingFloodWaitingProcessor : ICmdProcessor -{ - public string Usage - { - get { return "PINGFLW [ ]"; } - } - - public string Keyword - { - get { return "PINGFLW"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 100000; - if (args.Length > 0) - { - if (args.Length != 2) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseLong(args[1]); - } - catch - { - return false; - } - } - - PingFloodWaiting(context, clientsCnt, requestsCnt); - return true; - } - - private void PingFloodWaiting(CommandProcessorContext context, int clientsCnt, long requestsCnt) - { - context.IsAsync(); - - var clients = new List>(); - var threads = new List(); - var doneEvent = new ManualResetEventSlim(false); - var clientsDone = 0; - long all = 0; - for (int i = 0; i < clientsCnt; i++) - { - var autoResetEvent = new AutoResetEvent(false); - var client = context._tcpTestClient.CreateTcpConnection( - context, - (_, __) => - { - Interlocked.Increment(ref all); - autoResetEvent.Set(); - }, - connectionClosed: (conn, err) => context.Fail(reason: "Connection was closed prematurely.")); - clients.Add(client); - - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - threads.Add(new Thread(() => - { - for (int j = 0; j < count; ++j) - { - var package = new TcpPackage(TcpCommand.Ping, Guid.NewGuid(), null); - client.EnqueueSend(package.AsByteArray()); - autoResetEvent.WaitOne(); - } - - if (Interlocked.Increment(ref clientsDone) == clientsCnt) - { - context.Success(); - doneEvent.Set(); - } - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - sw.Stop(); - clients.ForEach(x => x.Close()); - - var reqPerSec = (all + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", all, - sw.ElapsedMilliseconds, reqPerSec); - PerfUtils.LogData(Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds / all)); - - if (Interlocked.Read(ref all) == requestsCnt) - { - context.Success(); - } - else - { - context.Fail(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/PingProcessor.cs b/src/EventStore.TestClient/Commands/PingProcessor.cs deleted file mode 100644 index 064265d6a0..0000000000 --- a/src/EventStore.TestClient/Commands/PingProcessor.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class PingProcessor : ICmdProcessor -{ - public string Usage - { - get { return Keyword; } - } - - public string Keyword - { - get { return "PING"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - context.IsAsync(); - - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - var package = new TcpPackage(TcpCommand.Ping, Guid.NewGuid(), null); - context.Log.Information("[{ip}:{tcpPort}]: PING...", context._tcpTestClient.Options.Host, - context._tcpTestClient.Options.TcpPort); - conn.EnqueueSend(package.AsByteArray()); - }, - handlePackage: (conn, pkg) => - { - if (pkg.Command != TcpCommand.Pong) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - context.Log.Information("[{ip}:{tcpPort}]: PONG!", context._tcpTestClient.Options.Host, - context._tcpTestClient.Options.TcpPort); - context.Success(); - conn.Close(); - }, - connectionClosed: (typedConnection, error) => - context.Fail(reason: "Connection was closed prematurely.")); - context.WaitForCompletion(); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/ReadAllProcessor.cs b/src/EventStore.TestClient/Commands/ReadAllProcessor.cs deleted file mode 100644 index a339771a6a..0000000000 --- a/src/EventStore.TestClient/Commands/ReadAllProcessor.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class ReadAllProcessor : ICmdProcessor -{ - public string Usage - { - get { return "RDALL [[F|B] [ []]]"; } - } - - public string Keyword - { - get { return "RDALL"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - bool forward = true; - long commitPos = 0; - long preparePos = 0; - bool positionOverridden = false; - bool resolveLinkTos = false; - bool requireLeader = false; - - if (args.Length > 0) - { - if (args.Length != 1 && args.Length != 3 && args.Length != 4) - { - return false; - } - - if (args[0].ToUpper() == "F") - { - forward = true; - } - else if (args[0].ToUpper() == "B") - { - forward = false; - } - else - { - return false; - } - - if (args.Length >= 3) - { - positionOverridden = true; - if (!long.TryParse(args[1], out commitPos) || !long.TryParse(args[2], out preparePos)) - { - return false; - } - } - - if (args.Length >= 4) - { - requireLeader = bool.Parse(args[3]); - } - } - - if (!positionOverridden) - { - commitPos = forward ? 0 : -1; - preparePos = forward ? 0 : -1; - } - - context.IsAsync(); - - int total = 0; - var sw = new Stopwatch(); - var tcpCommand = forward ? TcpCommand.ReadAllEventsForward : TcpCommand.ReadAllEventsBackward; - var tcpCommandToReceive = forward ? TcpCommand.ReadAllEventsForwardCompleted : TcpCommand.ReadAllEventsBackwardCompleted; - - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information("[{remoteEndPoint}, L{localEndPoint}]: Reading all {readDirection}...", - conn.RemoteEndPoint, conn.LocalEndPoint, forward ? "FORWARD" : "BACKWARD"); - - var readDto = - new ReadAllEvents(commitPos, preparePos, 10, resolveLinkTos, requireLeader); - var package = new TcpPackage(tcpCommand, Guid.NewGuid(), readDto.Serialize()).AsByteArray(); - sw.Start(); - conn.EnqueueSend(package); - }, - handlePackage: (conn, pkg) => - { - if (pkg.Command != tcpCommandToReceive) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Events.IsEmpty()) - { - sw.Stop(); - context.Log.Information("=== Reading ALL {readDirection} completed in {elapsed}. Total read: {total}", - forward ? "FORWARD" : "BACKWARD", sw.Elapsed, total); - context.Success(); - conn.Close(); - return; - } - - total += dto.Events.Count; - - var readDto = new ReadAllEvents(dto.NextCommitPosition, dto.NextPreparePosition, - 10, resolveLinkTos, requireLeader); - var package = new TcpPackage(tcpCommand, Guid.NewGuid(), readDto.Serialize()).AsByteArray(); - conn.EnqueueSend(package); - }, - connectionClosed: (connection, error) => context.Fail(reason: "Connection was closed prematurely.")); - - context.WaitForCompletion(); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/ReadFloodProcessor.cs b/src/EventStore.TestClient/Commands/ReadFloodProcessor.cs deleted file mode 100644 index 14e07b5e9e..0000000000 --- a/src/EventStore.TestClient/Commands/ReadFloodProcessor.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using EventStore.Client.Messages; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class ReadFloodProcessor : ICmdProcessor -{ - public string Usage - { - // 0 1 2 3 4 - get { return "RDFL [ [ [ []]]]"; } - } - - public string Keyword - { - get { return "RDFL"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 5000; - int streamsCnt = 1; - var streamPrefix = "test-stream"; - bool resolveLinkTos = false; - bool requireLeader = false; - if (args.Length > 0) - { - if (args.Length != 2 && args.Length != 3 && args.Length != 4 && args.Length != 5) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseLong(args[1]); - if (args.Length >= 3) - { - streamsCnt = MetricPrefixValue.ParseInt(args[2]); - } - - if (args.Length >= 4) - { - streamPrefix = args[3]; - } - - if (args.Length >= 5) - { - requireLeader = bool.Parse(args[4]); - } - } - catch - { - return false; - } - } - - RequestMonitor monitor = new RequestMonitor(); - ReadFlood(context, streamPrefix, clientsCnt, requestsCnt, streamsCnt, resolveLinkTos, requireLeader, monitor); - return true; - } - - private void ReadFlood(CommandProcessorContext context, string streamPrefix, int clientsCnt, long requestsCnt, int streamsCnt, - bool resolveLinkTos, bool requireLeader, RequestMonitor monitor) - { - context.IsAsync(); - - string[] streams = streamsCnt == 1 - ? new[] { streamPrefix } - : Enumerable.Range(0, streamsCnt).Select(x => $"{streamPrefix}-{x}").ToArray(); - - context.Log.Information("Reading streams {first} through to {last}", - streams.FirstOrDefault(), - streams.LastOrDefault()); - - var clients = new List>(); - var threads = new List(); - var doneEvent = new ManualResetEventSlim(false); - var sw2 = new Stopwatch(); - long succ = 0; - long fail = 0; - long all = 0; - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - long received = 0; - long sent = 0; - var client = context._tcpTestClient.CreateTcpConnection( - context, - (conn, pkg) => - { - if (pkg.Command != TcpCommand.ReadEventCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - monitor.EndOperation(pkg.CorrelationId); - if (dto.Result == ReadEventCompleted.Types.ReadEventResult.Success) - { - if (Interlocked.Increment(ref succ) % 1000 == 0) - { - Console.Write("."); - } - } - else - { - if (Interlocked.Increment(ref fail) % 1000 == 0) - { - Console.Write("#"); - } - } - - Interlocked.Increment(ref received); - var localAll = Interlocked.Increment(ref all); - if (localAll % 100000 == 0) - { - var elapsed = sw2.Elapsed; - sw2.Restart(); - context.Log.Information("\nDONE TOTAL {reads} READS IN {elapsed} ({rate:0.0}/s).", localAll, - elapsed, 1000.0 * 100000 / elapsed.TotalMilliseconds); - } - - if (localAll == requestsCnt) - { - doneEvent.Set(); - } - }, - connectionClosed: (conn, err) => context.Fail(reason: "Connection was closed prematurely.")); - clients.Add(client); - - var clientNum = i; - threads.Add(new Thread(() => - { - int streamIndex = (streamsCnt / clientsCnt) * clientNum; - context.Log.Information("Reader #{clientNum} performing {count} reads on {streamsCnt} streams starting at stream index {streamIndex}", - clientNum, count, streamsCnt, streamIndex); - for (int j = 0; j < count; ++j) - { - var corrId = Guid.NewGuid(); - - var eventStreamId = streams[streamIndex++]; - if (streamIndex >= streamsCnt) - { - streamIndex = 0; - } - - var read = new ReadEvent(eventStreamId, 0, resolveLinkTos, requireLeader); - var package = new TcpPackage(TcpCommand.ReadEvent, corrId, read.Serialize()); - monitor.StartOperation(corrId); - client.EnqueueSend(package.AsByteArray()); - - var localSent = Interlocked.Increment(ref sent); - while (localSent - Interlocked.Read(ref received) > - context._tcpTestClient.Options.ReadWindow / clientsCnt) - { - Thread.Sleep(1); - } - } - context.Log.Information("Reader #{clientNum} done", clientNum); - - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - sw2.Start(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - sw.Stop(); - clients.ForEach(client => client.Close()); - - var reqPerSec = (all + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("Completed. READS succ: {success}, fail: {failures}.", Interlocked.Read(ref succ), - Interlocked.Read(ref fail)); - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", all, - sw.ElapsedMilliseconds, reqPerSec); - monitor.GetMeasurementDetails(); - PerfUtils.LogData(Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("readsCnt", all))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - - if (succ != requestsCnt) - { - context.Fail(reason: "There were errors or not all requests completed."); - } - else - { - context.Success(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/ReadProcessor.cs b/src/EventStore.TestClient/Commands/ReadProcessor.cs deleted file mode 100644 index b9d004d9a6..0000000000 --- a/src/EventStore.TestClient/Commands/ReadProcessor.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Diagnostics; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class ReadProcessor : ICmdProcessor -{ - public string Usage - { - get { return "RD [ [ []]]"; } - } - - public string Keyword - { - get { return "RD"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - var eventStreamId = "test-stream"; - var fromNumber = 0; - const bool resolveLinkTos = false; - var requireLeader = false; - - if (args.Length > 0) - { - if (args.Length > 3) - { - return false; - } - - eventStreamId = args[0]; - if (args.Length >= 2) - { - fromNumber = MetricPrefixValue.ParseInt(args[1]); - } - - if (args.Length >= 3) - { - requireLeader = bool.Parse(args[2]); - } - } - - context.IsAsync(); - - var sw = new Stopwatch(); - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information("[{remoteEndPoint}, L{localEndPoint}]: Reading...", conn.RemoteEndPoint, - conn.LocalEndPoint); - var readDto = - new ReadEvent(eventStreamId, fromNumber, resolveLinkTos, requireLeader); - var package = - new TcpPackage(TcpCommand.ReadEvent, Guid.NewGuid(), readDto.Serialize()).AsByteArray(); - sw.Start(); - conn.EnqueueSend(package); - }, - handlePackage: (conn, pkg) => - { - sw.Stop(); - context.Log.Information("Read request took: {elapsed}.", sw.Elapsed); - - if (pkg.Command != TcpCommand.ReadEventCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - context.Log.Information("READ events from <{stream}>:\n\n" - + "\tEventStreamId: {stream}\n" - + "\tEventNumber: {eventNumber}\n" - + "\tReadResult: {readResult}\n" - + "\tEventType: {eventType}\n" - + "\tData: {data}\n" - + "\tMetadata: {metadata}\n", - eventStreamId, - eventStreamId, - dto.Event.Event.EventNumber, - dto.Result, - dto.Event.Event.EventType, - Helper.UTF8NoBom.GetString(dto.Event.Event.Data.ToByteArray()), - Helper.UTF8NoBom.GetString(dto.Event.Event.Metadata.ToByteArray())); - - - if (dto.Result == ReadEventCompleted.Types.ReadEventResult.Success) - { - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds)); - context.Success(); - } - else - { - context.Fail(); - } - - conn.Close(); - }, - connectionClosed: (connection, error) => context.Fail(reason: "Connection was closed prematurely.")); - - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/RequestMonitor.cs b/src/EventStore.TestClient/Commands/RequestMonitor.cs index 06580f521f..59328bd85d 100644 --- a/src/EventStore.TestClient/Commands/RequestMonitor.cs +++ b/src/EventStore.TestClient/Commands/RequestMonitor.cs @@ -5,10 +5,6 @@ using System.Linq; using System.Text; using System.Threading; -using EventStore.Core.Data; -using EventStore.Core.Messages; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; namespace EventStore.TestClient.Commands; diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs deleted file mode 100644 index d7e5af6f4d..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/BankAccountEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using EventStore.ClientAPI; -using EventStore.Common.Utils; -using EventStore.TestClient.Commands; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class BankAccountEvent -{ - public static EventData FromEvent(object accountObject) - { - if (accountObject == null) - { - throw new ArgumentNullException("accountObject"); - } - - var type = accountObject.GetType().Name; - var encodedData = Helper.UTF8NoBom.GetBytes(TestClientJson.To(accountObject)); - var encodedMetadata = - Helper.UTF8NoBom.GetBytes(TestClientJson.To(new Dictionary { { "IsEmpty", true } })); - - return new EventData(Guid.NewGuid(), type, true, encodedData, encodedMetadata); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/IScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/IScenario.cs deleted file mode 100644 index 1cfca4cdeb..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/IScenario.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal interface IScenario : IDisposable -{ - void Run(); - void Clean(); -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/JsonEventContainer.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/JsonEventContainer.cs deleted file mode 100644 index 09aaac7c54..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/JsonEventContainer.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using EventStore.ClientAPI; -using EventStore.Common.Utils; -using EventStore.TestClient.Commands; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal static class JsonEventContainer -{ - public static EventData ForEvent(object @event) - { - if (@event == null) - { - throw new ArgumentNullException("event"); - } - - var encodedData = Helper.UTF8NoBom.GetBytes(TestClientJson.To(@event)); - var encodedMetadata = - Helper.UTF8NoBom.GetBytes(TestClientJson.To(new Dictionary { { "IsEmpty", true } })); - - return new EventData(Guid.NewGuid(), @event.GetType().Name, true, encodedData, encodedMetadata); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingProjTranWriteScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingProjTranWriteScenario.cs deleted file mode 100644 index c00285cd6e..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingProjTranWriteScenario.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class LoopingProjTranWriteScenario : ProjectionsScenarioBase -{ - private readonly TimeSpan _executionPeriod; - - private readonly Random _random; - - public LoopingProjTranWriteScenario(Action directSendOverTcp, - int maxConcurrentRequests, - int connections, - int streams, - int eventsPerStream, - int streamDeleteStep, - TimeSpan executionPeriod, - string dbParentPath, - NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - _executionPeriod = executionPeriod; - _random = new Random(); - } - - private EventData CreateEventA(int version) - { - var @event = JsonEventContainer.ForEvent(new VersionnedEventA(version)); - return @event; - } - - private EventData CreateEventB(int version) - { - var @event = JsonEventContainer.ForEvent(new VersionnedEventB(version)); - return @event; - } - - internal class VersionnedEvent - { - public readonly int Version; - - public VersionnedEvent(int version) - { - Version = version; - } - } - - internal class VersionnedEventA : VersionnedEvent - { - public VersionnedEventA(int version) : base(version) - { - } - } - - internal class VersionnedEventB : VersionnedEvent - { - public VersionnedEventB(int version) : base(version) - { - } - } - - protected override void RunInternal() - { - var stopWatch = Stopwatch.StartNew(); - - var runIndex = 0; - while (stopWatch.Elapsed < _executionPeriod) - { - var msg = string.Format( - "=================== Start run #{0}, elapsed {1} of {2} minutes, {3} =================== ", - runIndex, - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information( - "=================== Start run #{runIndex}, elapsed {elapsed} of {executionPeriod} minutes, {type} =================== ", - runIndex, - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information("##teamcity[message '{message}']", msg); - - InnerRun(runIndex); - runIndex += 1; - } - } - - private void InnerRun(int runIndex) - { - var nodeId = StartNode(); - - try - { - EnableProjectionByCategory(); - - var streamA = string.Format("numberedevents{0}-stream_a", runIndex); - var streamB = string.Format("numberedevents{0}-stream_b", runIndex); - - var store = GetConnection(); - - var writtenCountA = 0; - var writtenCountB = 0; - while (writtenCountA + writtenCountB < EventsPerStream) - { - var batchSizeA = _random.Next(1, EventsPerStream / 10); - var batchSizeB = _random.Next(1, EventsPerStream / 10); - - var transactionA = store.StartTransactionAsync(streamA, ExpectedVersion.Any).Result; - - var w1 = WriteTransactionData(transactionA, writtenCountA, batchSizeA, CreateEventA); - w1.Wait(); - - var transactionB = store.StartTransactionAsync(streamB, ExpectedVersion.Any).Result; - var w2 = WriteTransactionData(transactionB, writtenCountB, batchSizeB, CreateEventB); - w2.Wait(); - - var cB = CommitTransaction(transactionB); - cB.Wait(); - - var cA = CommitTransaction(transactionA); - cA.Wait(); - - writtenCountA += batchSizeA; - writtenCountB += batchSizeB; - } - - var projectionName = string.Format("NumberedByType{0}", runIndex); - var projection = string.Format(@" - fromCategory('numberedevents{0}') - .when({{ - $init: function() {{ - return {{aVer:0, aList:'', bVer:0, bList:''}}; - }}, - VersionnedEventA: function(state, event) {{ - state.aVer += 1; - if (state.aVer != event.body.version) {{ - throw JSON.stringify({{ - message: 'Version in A is incorrect. ', - stream: event.streamId, - seqNumber: event.sequenceNumber, - eventType: event.eventType, - eventInternalVer: event.body.version, - detailsAVer: state.aVer, - detailsBVer: state.bVer}}); - }} - return state; - }}, - VersionnedEventB: function(state, event) {{ - state.bVer += 1; - if (state.bVer != event.body.version) {{ - throw JSON.stringify({{ - message: 'Version in B is incorrect. ', - stream: event.streamId, - seqNumber: event.sequenceNumber, - eventType: event.eventType, - eventInternalVer: event.body.version, - detailsAVer: state.aVer, - detailsBVer: state.bVer }}); - }} - return state; - }} - }}) -", runIndex); - - var projectionManager = GetProjectionsManager(); - projectionManager.CreateContinuousAsync(projectionName, projection, AdminCredentials).Wait(); - - WaitAndCheckIfIsFaulted(projectionName); - - Log.Debug("Done iteration {runIndex}", runIndex); - } - finally - { - KillNode(nodeId); - } - } - - private Task WriteTransactionData(EventStoreTransaction transaction, int startingVersion, - int eventCount, Func createEvent) - { - Log.Information("Starting to write {eventCount} events in transaction {transactionId}", eventCount, - transaction.TransactionId); - - var resSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - Action fail = prevTask => - { - Log.Information("WriteEventsInTransactionalWay for transaction {transactionId} failed.", - transaction.TransactionId); - resSource.SetException(prevTask.Exception); - }; - - int version = startingVersion; - - Action writeTransactionEvent = null; - writeTransactionEvent = _ => - { - if (version == startingVersion + eventCount) - { - resSource.SetResult(null); - return; - } - - version += 1; - - var writeTask = transaction.WriteAsync(new[] { createEvent(version) }); - writeTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - writeTask.ContinueWith(writeTransactionEvent, TaskContinuationOptions.OnlyOnRanToCompletion); - }; - - Task.Factory.StartNew(() => writeTransactionEvent(null)); - - return resSource.Task; - } - - private Task CommitTransaction(EventStoreTransaction transaction) - { - var resSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - Action fail = prevTask => - { - Log.Information("WriteEventsInTransactionalWay for transaction {transactionId} failed", - transaction.TransactionId); - resSource.SetException(prevTask.Exception); - }; - - var commitTask = transaction.CommitAsync(); - commitTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - commitTask.ContinueWith(t => - { - Log.Information("Committed transaction {transactionId}", transaction.TransactionId); - resSource.SetResult(null); - }, TaskContinuationOptions.OnlyOnRanToCompletion); - - return resSource.Task; - } - - private void WaitAndCheckIfIsFaulted(string projectionName) - { - var stopWatch = Stopwatch.StartNew(); - - var waitDuration = TimeSpan.FromMilliseconds(20 * 1000 + 5 * Streams * EventsPerStream); - while (stopWatch.Elapsed < waitDuration) - { - string reason; - var failed = GetProjectionIsFaulted(projectionName, out reason); - if (failed) - { - var message = string.Format("Projection {0} failed, reason:\n{1}", projectionName, reason); - throw new ApplicationException(message); - } - - var position = GetProjectionPosition(projectionName); - if (position >= (EventsPerStream - 1)) - { - Log.Debug("Expected position reached, done."); - break; - } - - Thread.Sleep(2000); - } - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingProjectionKillScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingProjectionKillScenario.cs deleted file mode 100644 index 803773c5e7..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingProjectionKillScenario.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class LoopingProjectionKillScenario : ProjectionsKillScenario -{ - private TimeSpan _executionPeriod; - - private int _iterationCode; - private readonly TimeSpan _iterationLoopDuration; - private readonly TimeSpan _firstKillInterval; - - public LoopingProjectionKillScenario(Action directSendOverTcp, int maxConcurrentRequests, - int connections, int streams, int eventsPerStream, int streamDeleteStep, TimeSpan executionPeriod, - string dbParentPath, NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - _executionPeriod = executionPeriod; - - _iterationLoopDuration = TimeSpan.FromMilliseconds(10 * (Streams * EventsPerStream + Streams) + 20 * 1000); - _firstKillInterval = TimeSpan.FromSeconds(_iterationLoopDuration.TotalSeconds / 2); - } - - protected override int GetIterationCode() - { - return _iterationCode; - } - - protected void SetNextIterationCode() - { - _iterationCode += 1; - } - - protected override void RunInternal() - { - var nodeProcessId = StartNode(); - EnableProjectionByCategory(); - - var stopWatch = Stopwatch.StartNew(); - - while (stopWatch.Elapsed < _executionPeriod) - { - var msg = string.Format( - "=================== Start run #{0}, elapsed {1} of {2} minutes, {3} =================== ", - GetIterationCode(), - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information( - "=================== Start run #{iteration}, elapsed {elapsed} of {executionPeriod} minutes, {type} =================== ", - GetIterationCode(), - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information("##teamcity[message '{message}']", msg); - - var iterationTask = RunIteration(); - - Thread.Sleep(_firstKillInterval); - - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - - if (!iterationTask.Wait(_iterationLoopDuration)) - { - throw new TimeoutException("Iteration execution timeout."); - } - - if (iterationTask.Result != true) - { - throw new ApplicationException("Iteration faulted.", iterationTask.Exception); - } - - SetNextIterationCode(); - } - } - - private Task RunIteration() - { - var countItem = CreateCountItem(); - var sumCheckForBankAccount0 = CreateSumCheckForBankAccount0(); - - var writeTask = WriteData(); - - var expectedAllEventsCount = (Streams * EventsPerStream).ToString(); - var lastExpectedEventVersion = (EventsPerStream - 1).ToString(); - - var successTask = Task.Factory.StartNew(() => - { - var success = false; - var stopWatch = new Stopwatch(); - while (stopWatch.Elapsed < _iterationLoopDuration) - { - if (writeTask.IsFaulted) - { - throw new ApplicationException("Failed to write data"); - } - - if (writeTask.IsCompleted && !stopWatch.IsRunning) - { - stopWatch.Start(); - } - - success = CheckProjectionState(countItem, "count", x => x == expectedAllEventsCount) - && CheckProjectionState(sumCheckForBankAccount0, "success", - x => x == lastExpectedEventVersion); - - if (success) - { - break; - } - - Thread.Sleep(4000); - } - - if (!CheckProjectionState(countItem, "count", x => x == expectedAllEventsCount)) - { - Log.Error( - "Projection '{projection}' has not completed with expected result {expectedCount} in time. ", - countItem, expectedAllEventsCount); - } - - if (!CheckProjectionState(sumCheckForBankAccount0, "success", x => x == lastExpectedEventVersion)) - { - Log.Error( - "Projection '{projection}' has not completed with expected result {lastExpectedEventVersion} in time.", - sumCheckForBankAccount0, lastExpectedEventVersion); - } - - return success; - }); - - writeTask.Wait(); - - return successTask; - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingScenario.cs deleted file mode 100644 index ec71c09579..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/LoopingScenario.cs +++ /dev/null @@ -1,201 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Threading.Tasks; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class LoopingScenario : ScenarioBase -{ - private readonly TimeSpan _executionPeriod; - - private readonly Random _rnd = new Random(); - private volatile bool _stopParalleWrites; - private TimeSpan _startupWaitInterval; - - protected override TimeSpan StartupWaitInterval - { - get { return _startupWaitInterval; } - } - - public LoopingScenario(Action directSendOverTcp, - int maxConcurrentRequests, - int connections, - int streams, - int eventsPerStream, - int streamDeleteStep, - TimeSpan executionPeriod, - string dbParentPath, - NodeConnectionInfo customNodeConnection) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNodeConnection) - { - _executionPeriod = executionPeriod; - SetStartupWaitInterval(TimeSpan.FromSeconds(10)); - } - - private void SetStartupWaitInterval(TimeSpan interval) - { - _startupWaitInterval = interval; - } - - protected override void RunInternal() - { - var stopWatch = Stopwatch.StartNew(); - - var runIndex = 0; - while (stopWatch.Elapsed < _executionPeriod) - { - var msg = string.Format( - "=================== Start run #{0}, elapsed {1} of {2} minutes, {3} =================== ", - runIndex, - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information( - "=================== Start run #{runIndex}, elapsed {elapsed} of {executionPeriod} minutes, {type} =================== ", - runIndex, - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information("##teamcity[message '{message}']", msg); - - SetStartupWaitInterval(TimeSpan.FromSeconds(10 + (2 * (runIndex % 200)))); - InnerRun(runIndex); - runIndex += 1; - } - } - - protected virtual void InnerRun(int runIndex) - { - var nodeProcessId = StartNode(); - - var parallelWritesTimeout = TimeSpan.FromMinutes((EventsPerStream / 1000.0) * 10); - - var parallelWriteTask = RunParallelWrites(runIndex); - - var streams = Enumerable.Range(0, Streams).Select(i => FormatStreamName(runIndex, i)).ToArray(); - - var slices = Split(streams, 3); - var singleEventSlice = slices.Take(1).SelectMany(x => x).ToArray(); - var batchSlice = slices.Skip(1).Take(1).SelectMany(x => x).ToArray(); - var transSlice = slices.Skip(2).Take(1).SelectMany(x => x).ToArray(); - - var wr1 = Write(WriteMode.SingleEventAtTime, singleEventSlice, EventsPerStream); - var wr2 = Write(WriteMode.Bucket, batchSlice, EventsPerStream); - var wr3 = Write(WriteMode.Transactional, transSlice, EventsPerStream); - - if (runIndex % 4 == 0) - { - Scavenge(); - } - - Task.WaitAll(wr1, wr2, wr3); - - var deleted = streams.Where((s, i) => i % StreamDeleteStep == 0).ToArray(); - DeleteStreams(deleted); - - _stopParalleWrites = true; - if (!parallelWriteTask.Wait(parallelWritesTimeout)) - { - throw new ApplicationException("Parallel writes stop timed out, 1."); - } - - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - - parallelWriteTask = RunParallelWrites(runIndex); - - var dl1 = CheckStreamsDeleted(deleted); - - var exceptDeleted = streams.Except(deleted).ToArray(); - - var readCnt = Math.Min(100, EventsPerStream / 3); - var rd1 = Read(exceptDeleted, @from: 0, count: readCnt); - var rd2 = Read(exceptDeleted, @from: EventsPerStream - readCnt, count: readCnt); - var rd3 = Read(exceptDeleted, @from: EventsPerStream / 2, - count: Math.Min(readCnt, EventsPerStream - EventsPerStream / 2)); - - Log.Information("== READ from picked ALL =="); - - var allExistingStreamsSlice = (from run in Enumerable.Range(0, runIndex + 1) - from streamNum in Enumerable.Range(0, Streams) - where streamNum % StreamDeleteStep != 0 - where _rnd.NextDouble() < 0.1 - select FormatStreamName(run, streamNum)).ToArray(); - - var allDeletedStreamsSlice = (from run in Enumerable.Range(0, runIndex + 1) - from streamNum in Enumerable.Range(0, Streams) - where streamNum % StreamDeleteStep == 0 - where _rnd.NextDouble() < 0.1 - select FormatStreamName(run, streamNum)).ToArray(); - - var prevCheckTasks = new List(); - if (allExistingStreamsSlice.Length > 0) - { - var rd4 = Read(allExistingStreamsSlice, 0, Math.Max(1, EventsPerStream / 5)); - var rd5 = Read(allExistingStreamsSlice, EventsPerStream / 2, Math.Max(1, EventsPerStream / 5)); - - prevCheckTasks.Add(rd4); - prevCheckTasks.Add(rd5); - } - - if (allDeletedStreamsSlice.Length > 0) - { - var dl2 = CheckStreamsDeleted(allDeletedStreamsSlice); - prevCheckTasks.Add(dl2); - } - - Task.WaitAll(new[] { dl1, rd1, rd2, rd3 }.Union(prevCheckTasks).ToArray()); - - _stopParalleWrites = true; - if (!parallelWriteTask.Wait(parallelWritesTimeout)) - { - throw new ApplicationException("Parallel writes stop timed out, 2."); - } - - KillNode(nodeProcessId); - } - - protected virtual Task RunParallelWrites(int runIndex) - { - _stopParalleWrites = false; - - return Task.Factory.StartNew(() => - { - int index = 0; - while (!_stopParalleWrites) - { - Log.Debug("Start RunParallelWrites #{index} for runIndex {runIndex}", index, runIndex); - - var parallelStreams = Enumerable.Range(0, 2) - .Select(x => string.Format("parallel-write-stream-in{0}-{1}-{2}", - runIndex, - x, - string.Format("rnd{0}-{1}", _rnd.Next(), DateTime.UtcNow.Ticks))) - .ToArray(); - - var wr = Write(WriteMode.SingleEventAtTime, parallelStreams, EventsPerStream); - wr.Wait(); - - Log.Debug("Wrote RunParallelWrites #{index} for runIndex {runIndex}", index, runIndex); - - var rd1 = Read(parallelStreams, 0, EventsPerStream / 6); - var rd2 = Read(parallelStreams, EventsPerStream / 3, EventsPerStream / 6); - var rd3 = Read(parallelStreams, EventsPerStream - EventsPerStream / 10, EventsPerStream / 10); - Task.WaitAll(rd1, rd2, rd3); - - Log.Debug("Done RunParallelWrites #{index} for runIndex {runIndex}", index, runIndex); - - index += 1; - } - }, TaskCreationOptions.LongRunning); - } - - protected static string FormatStreamName(int runIndex, int i) - { - return string.Format("stream-in{0}-{1}", runIndex, i); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/MassProjectionsScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/MassProjectionsScenario.cs deleted file mode 100644 index 1a94c9433f..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/MassProjectionsScenario.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class MassProjectionsScenario : ProjectionsKillScenario -{ - private readonly Random _random = new Random(); - - public MassProjectionsScenario(Action directSendOverTcp, int maxConcurrentRequests, - int threads, int streams, int eventsPerStream, int streamDeleteStep, string dbParentPath, - NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, threads, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - } - - protected override void RunInternal() - { - var success = true; - - var nodeProcessId = StartNode(); - EnableProjectionByCategory(); - - var countProjections = new List(); - var bankProjections = new List(); - - var writeTasks = new List(); - - while (GetIterationCode() < Streams / 3) - { - writeTasks.Add(WriteData()); - - SetNextIterationCode(); - - countProjections.Add(CreateCountItem()); - bankProjections.Add(CreateSumCheckForBankAccount0()); - - Log.Information("Created {bankProjections} and {countProjections}", bankProjections[bankProjections.Count - 1], - countProjections[countProjections.Count - 1]); - } - - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - - writeTasks.Add(WriteData()); - - var writeTask = Task.Factory.ContinueWhenAll(writeTasks.ToArray(), tsks => Log.Information("All Data written")); - - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - - int count = 10; - while (count > 0) - { - Log.Information("Stop and start projection, remaining iterations {count}, waiting for data to be written.", - count); - - StartOrStopProjection(countProjections, false); - StartOrStopProjection(bankProjections, false); - - Thread.Sleep(TimeSpan.FromSeconds(10)); - - StartOrStopProjection(countProjections, true); - StartOrStopProjection(bankProjections, true); - - if (writeTask.IsCompleted) - { - count -= 1; - } - - if (writeTask.IsFaulted) - { - throw new ApplicationException("Failed to write data", writeTask.Exception); - } - - success = CheckProjectionState(bankProjections[bankProjections.Count - 1], - "success", - x => x == (EventsPerStream - 1).ToString()); - if (success) - { - break; - } - - var sleepTimeSeconds = 10 + Streams * EventsPerStream / 1000.0; - Log.Information("Sleep 1 for {sleepTime} seconds, remaining count {count}", sleepTimeSeconds, count); - Thread.Sleep(TimeSpan.FromSeconds(sleepTimeSeconds)); - } - - writeTask.Wait(); - - count = 20; - success = false; - while (!success && count > 0) - { - Log.Information("Wait until projections are computed, remaining iterations {count}", count); - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - - success = CheckProjectionState(bankProjections[bankProjections.Count - 1], - "success", - x => x == (EventsPerStream - 1).ToString()); - - if (success) - { - break; - } - - var sleepTimeSeconds = 10 + (Streams * EventsPerStream) / 500; - Log.Information("Sleep 2 for {sleepTime} seconds, remaining count {count}", sleepTimeSeconds, count); - Thread.Sleep(TimeSpan.FromSeconds(sleepTimeSeconds)); - - count -= 1; - } - - if (!success) - { - throw new ApplicationException("Last bank projection failed"); - } - } - - private void StartOrStopProjection(IEnumerable projections, bool enable) - { - var manager = GetProjectionsManager(); - const int retriesNumber = 5; - - foreach (var projection in projections.ToArray()) - { - var retry = 0; - - int shortWait = 50 + _random.Next(100); - Log.Debug("Wait for {waitTime}ms before next enable/disable projection", shortWait); - Thread.Sleep(shortWait); - - while (retry <= retriesNumber) - { - var isRunning = GetProjectionIsRunning(projection); - - try - { - if (enable) - { - if (!isRunning) - { - manager.EnableAsync(projection, AdminCredentials).Wait(); - } - else - { - Log.Information("Projection '{projection}' is already running and will not be enabled.", - projection); - } - } - else - { - if (isRunning) - { - manager.DisableAsync(projection, AdminCredentials).Wait(); - } - else - { - Log.Information( - "Projection '{projection}' is already not running and will not be disabled again.", - projection); - } - } - - break; - } - catch (Exception ex) - { - var waitForMs = 5000 + (retry * (3000 + _random.Next(2000))); - - Log.Information(ex, - "Failed to StartOrStopProjection (enable:{enable}; isRunning:{isRunning}) projection {projection}, retry #{retry}, wait {waitTime}ms", - enable, - isRunning, - projection, - retry, - waitForMs); - - if (retry != 0) - { - Thread.Sleep(waitForMs); - } - - if (retry == retriesNumber) - { - throw new ApplicationException(string.Format( - "Failed to StartOrStopProjection (enable:{0}) projection {1}," + - " max number ({2}) of retries reached.", - enable, - projection, - retry), - ex); - } - } - - - retry++; - } - } - } - - private int _iterationCode; - - protected override int GetIterationCode() - { - return _iterationCode; - } - - private void SetNextIterationCode() - { - _iterationCode += 1; - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/NodeConnectionInfo.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/NodeConnectionInfo.cs deleted file mode 100644 index 8c703f089c..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/NodeConnectionInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Net; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class NodeConnectionInfo -{ - public IPAddress IpAddress { get; private set; } - public int TcpPort { get; private set; } - public int HttpPort { get; private set; } - - public NodeConnectionInfo(IPAddress ipAddress, int tcpPort, int httPort) - { - IpAddress = ipAddress; - TcpPort = tcpPort; - HttpPort = httPort; - } - - public override string ToString() - { - return string.Format("[{0}:{1}:{2}]", IpAddress, TcpPort, HttpPort); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjForeachForcedCommonNameScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/ProjForeachForcedCommonNameScenario.cs deleted file mode 100644 index 5f66b83d7d..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjForeachForcedCommonNameScenario.cs +++ /dev/null @@ -1,286 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.TestClient.Commands.DvuBasic; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class ProjForeachForcedCommonNameScenario : ProjectionsScenarioBase -{ - private int _iterationCode = 0; - private TimeSpan _executionPeriod; - - public ProjForeachForcedCommonNameScenario(Action directSendOverTcp, - int maxConcurrentRequests, int connections, int streams, int eventsPerStream, int streamDeleteStep, - TimeSpan executionPeriod, string dbParentPath, NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - _executionPeriod = executionPeriod; - } - - private EventData CreateBankEvent(int version) - { - var accountObject = BankAccountEventFactory.CreateAccountObject(version); - var @event = BankAccountEvent.FromEvent(accountObject); - return @event; - } - - protected virtual int GetIterationCode() - { - return _iterationCode; - } - - protected virtual bool ShouldRestartNode - { - get { return true; } - } - - protected override void RunInternal() - { - var started = DateTime.UtcNow; - - while ((DateTime.UtcNow - started) < _executionPeriod) - { - var msg = string.Format( - "=================== Start run #{0}, elapsed {1} of {2} minutes, {3} =================== ", - GetIterationCode(), - (int)(DateTime.Now - started).TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information( - "=================== Start run #{iteration}, elapsed {elapsed} of {executionPeriod} minutes, {type} =================== ", - GetIterationCode(), - (int)(DateTime.Now - started).TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information("##teamcity[message '{message}']", msg); - - - InnerRun(); - _iterationCode += 1; - } - } - - private void InnerRun() - { - int nodeProcessId = -1; - - if (ShouldRestartNode) - { - nodeProcessId = StartNode(); - } - - EnableProjectionByCategory(); - - var sumCheckForBankAccounts = CreateSumCheckForBankAccounts("sumCheckForBankAccounts", "1"); - var sumCheckForBankAccounts2 = CreateSumCheckForBankAccounts("sumCheckForBankAccounts", "2"); - - var projections = new[] { sumCheckForBankAccounts, sumCheckForBankAccounts2 }; - - var writeTask = WriteData(); - - bool failed = false; - string failReason = null; - - var isWatchStarted = false; - var expectedAllEventsCount = Streams + (Streams * EventsPerStream); - - var stopWatch = new Stopwatch(); - - var waitDuration = TimeSpan.FromMilliseconds(20 * 1000 + 5 * Streams * EventsPerStream); - while (stopWatch.Elapsed < waitDuration) - { - if (writeTask.IsFaulted) - { - throw new ApplicationException("Failed to write data"); - } - - if (writeTask.IsCompleted && !stopWatch.IsRunning) - { - stopWatch.Start(); - isWatchStarted = true; - } - - if (isWatchStarted) - { - stopWatch.Stop(); - } - - failed = CheckIsFaulted(projections, out failReason); - if (failed) - { - break; - } - - if (CheckIsCompleted(projections, expectedAllEventsCount)) - { - break; - } - - Thread.Sleep((int)(waitDuration.TotalMilliseconds / 6)); - - failed = CheckIsFaulted(projections, out failReason); - if (failed) - { - break; - } - - if (ShouldRestartNode) - { - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - } - - if (isWatchStarted) - { - stopWatch.Start(); - } - } - - writeTask.Wait(); - - for (int i = 3; i < 11; ++i) - { - var newSumCheckForBankAccounts = CreateSumCheckForBankAccounts("sumCheckForBankAccounts", i.ToString()); - Thread.Sleep(200); - failed = CheckIsFaulted(new[] { newSumCheckForBankAccounts }, out failReason); - - if (failed) - { - break; - } - } - - if (ShouldRestartNode) - { - KillNode(nodeProcessId); - } - - if (failed) - { - throw new ApplicationException(string.Format("Projection failed due to reason: {0}.", failReason)); - } - } - - private bool CheckIsFaulted(IEnumerable projectionsNames, out string failReason) - { - var faulted = false; - failReason = null; - foreach (var projectionName in projectionsNames) - { - faulted = faulted || GetProjectionIsFaulted(projectionName, out failReason); - } - - return faulted; - } - - private bool CheckIsCompleted(IEnumerable projectionsNames, int expectedAllEventsCount) - { - var completed = false; - foreach (var projectionName in projectionsNames) - { - var position = GetProjectionPosition(projectionName); - if (position == expectedAllEventsCount) - { - Log.Debug("Expected position reached in {projection}, done.", projectionName); - completed = true; - } - } - - return completed; - } - - protected Task WriteData() - { - var streams = Enumerable.Range(0, Streams) - .Select(i => string.Format("bank_account_it{0}-{1}", GetIterationCode(), i)).ToArray(); - var slices = Split(streams, 3); - - var w1 = Write(WriteMode.SingleEventAtTime, slices[0], EventsPerStream, CreateBankEvent); - var w2 = Write(WriteMode.Bucket, slices[1], EventsPerStream, CreateBankEvent); - var w3 = Write(WriteMode.Transactional, slices[2], EventsPerStream, CreateBankEvent); - - var task = Task.Factory.ContinueWhenAll(new[] { w1, w2, w3 }, Task.WaitAll); - return task.ContinueWith(x => Log.Information("Data written for iteration {iteration}.", GetIterationCode())); - } - - protected string CreateSumCheckForBankAccounts(string projectionName, string suffix = "") - { - var fullProjectionName = string.Format("{0}_it{1}_{2}", projectionName, GetIterationCode(), suffix); - var countItemsProjection = string.Format(@" - fromCategory('bank_account_it{1}').foreachStream().when({{ - $init: function() {{ - return {{credited:0, credsum:'', debited:0, debsum:''}}; - }}, - AccountCredited: function (state, event) {{ - state.credited += event.body.creditedAmount; - /*state.credsum += '#' + event.sequenceNumber + ':' + event.body.creditedAmount + ';'*/ - }}, - AccountDebited: function (state, event) {{ - state.debited += event.body.debitedAmount; - /*state.debsum += '#' + event.sequenceNumber + ':' + event.body.debitedAmount + ';'*/ - }}, - AccountCheckPoint: function(state, event) {{ - if (state.credited != event.body.creditedAmount) {{ - throw JSON.stringify({{ - message: 'Credited amount is incorrect. ', - expected: event.body.creditedAmount, - actual: state.credited, - stream: event.streamId, - ver: event.sequenceNumber, - details: state.credsum }}); - }} - if (state.debited != event.body.debitedAmount) {{ - throw JSON.stringify({{ - message: 'Debited amount is incorrect. ', - expected: event.body.debitedAmount, - actual: state.debited, - stream: event.streamId, - ver: event.sequenceNumber, - details: state.debsum }}); - }} - state.success=event.sequenceNumber; - }} - }}) -", projectionName, GetIterationCode()); - - GetProjectionsManager().CreateContinuousAsync(fullProjectionName, countItemsProjection, AdminCredentials) - .Wait(); - - return fullProjectionName; - } -} - -internal class ProjForeachForcedCommonNameNoRestartScenario : ProjForeachForcedCommonNameScenario -{ - public ProjForeachForcedCommonNameNoRestartScenario(Action directSendOverTcp, - int maxConcurrentRequests, - int connections, - int streams, - int eventsPerStream, - int streamDeleteStep, - TimeSpan executionPeriod, - string dbParentPath, - NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - executionPeriod, dbParentPath, customNode) - { - } - - protected override bool ShouldRestartNode - { - get { return false; } - } - - protected override void RunInternal() - { - StartNode(); - base.RunInternal(); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionWrongTagCheck.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionWrongTagCheck.cs deleted file mode 100644 index 7d9000670e..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionWrongTagCheck.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Threading; -using System.Threading.Tasks; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class ProjectionWrongTagCheck : ProjectionsKillScenario -{ - private TimeSpan _executionPeriod; - - public ProjectionWrongTagCheck(Action directSendOverTcp, int maxConcurrentRequests, - int connections, int streams, int eventsPerStream, int streamDeleteStep, TimeSpan executionPeriod, - string dbParentPath, NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - _executionPeriod = executionPeriod; - } - - private int _iterationCode = 0; - - protected override int GetIterationCode() - { - return _iterationCode; - } - - protected void SetNextIterationCode() - { - _iterationCode += 1; - } - - protected override void RunInternal() - { - var nodeProcessId = StartNode(); - EnableProjectionByCategory(); - KillNode(nodeProcessId); - - var stopWatch = Stopwatch.StartNew(); - - while (stopWatch.Elapsed < _executionPeriod) - { - var msg = string.Format( - "=================== Start run #{0}, elapsed {1} of {2} minutes, {3} =================== ", - GetIterationCode(), - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - - Log.Information( - "=================== Start run #{iteration}, elapsed {elapsed} of {executionPeriod} minutes, {type} =================== ", - GetIterationCode(), - (int)stopWatch.Elapsed.TotalMinutes, - _executionPeriod.TotalMinutes, - GetType().Name); - Log.Information("##teamcity[message '{message}']", msg); - - var iterationTask = RunIteration(); - iterationTask.Wait(); - - SetNextIterationCode(); - } - } - - private Task RunIteration() - { - var nodeProcessId = StartNode(); - - - var sumCheckForBankAccount0 = CreateSumCheckForBankAccount0(); - - var writeTask = WriteData(); - - var lastExpectedEventVersion = (EventsPerStream - 1).ToString(); - - var successTask = Task.Factory.StartNew(() => - { - var success = true; - var stopWatch = new Stopwatch(); - - var wasLessLastTime = false; - while (stopWatch.Elapsed < TimeSpan.FromMilliseconds(10 * (Streams * EventsPerStream + Streams))) - { - if (!wasLessLastTime) - { - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - } - - if (writeTask.IsFaulted) - { - throw new ApplicationException("Failed to write data"); - } - - if (writeTask.IsCompleted && !stopWatch.IsRunning) - { - stopWatch.Start(); - } - - var count1 = GetProjectionStateValue(sumCheckForBankAccount0, "success", int.Parse, -1); - for (var i = 0; i < 5; ++i) - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - var count2 = GetProjectionStateValue(sumCheckForBankAccount0, "success", int.Parse, -1); - - if (count1 > count2) - { - if (wasLessLastTime) - { - success = false; - break; - } - - wasLessLastTime = true; - } - - count1 = count2; - } - - if (!success) - { - break; - } - - if (CheckProjectionState(sumCheckForBankAccount0, "success", x => x == lastExpectedEventVersion)) - { - break; - } - } - - KillNode(nodeProcessId); - - if (!success) - { - throw new ApplicationException(string.Format( - "Projection {0} has not completed with expected result {1} in time.", sumCheckForBankAccount0, - lastExpectedEventVersion)); - } - - return success; - }); - - return Task.Factory.ContinueWhenAll(new[] { writeTask, successTask }, tasks => - { - Log.Information("Iteration {iteration} tasks completed", GetIterationCode()); - Task.WaitAll(tasks); - Log.Information("Iteration {iteration} successful", GetIterationCode()); - }); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs deleted file mode 100644 index 38ac40b10b..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsKillScenario.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.TestClient.Commands.DvuBasic; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class ProjectionsKillScenario : ProjectionsScenarioBase -{ - public ProjectionsKillScenario(Action directSendOverTcp, - int maxConcurrentRequests, - int connections, - int streams, - int eventsPerStream, - int streamDeleteStep, - string dbParentPath, - NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - } - - private EventData CreateBankEvent(int version) - { - var accountObject = BankAccountEventFactory.CreateAccountObject(version); - var @event = BankAccountEvent.FromEvent(accountObject); - return @event; - } - - protected virtual int GetIterationCode() - { - return 0; - } - - protected override void RunInternal() - { - var nodeProcessId = StartNode(); - EnableProjectionByCategory(); - - var countItem = CreateCountItem(); - var sumCheckForBankAccount0 = CreateSumCheckForBankAccount0(); - - var writeTask = WriteData(); - - var success = false; - var expectedAllEventsCount = (Streams * EventsPerStream).ToString(); - var lastExpectedEventVersion = (EventsPerStream - 1).ToString(); - - var isWatchStarted = false; - - var stopWatch = new Stopwatch(); - - var waitDuration = TimeSpan.FromMilliseconds(20 * 1000 + 5 * Streams * EventsPerStream); - while (stopWatch.Elapsed < waitDuration) - { - if (writeTask.IsFaulted) - { - throw new ApplicationException("Failed to write data"); - } - - if (writeTask.IsCompleted && !stopWatch.IsRunning) - { - stopWatch.Start(); - isWatchStarted = true; - } - - success = CheckProjectionState(countItem, "count", x => x == expectedAllEventsCount) - && CheckProjectionState(sumCheckForBankAccount0, "success", - x => x == lastExpectedEventVersion); - - if (success) - { - break; - } - - if (isWatchStarted) - { - stopWatch.Stop(); - } - - Thread.Sleep((int)(waitDuration.TotalMilliseconds / 10)); - - KillNode(nodeProcessId); - nodeProcessId = StartNode(); - - if (isWatchStarted) - { - stopWatch.Start(); - } - } - - writeTask.Wait(); - - KillNode(nodeProcessId); - - if (!success) - { - throw new ApplicationException( - string.Format("Projections did not complete with expected result in time")); - } - } - - protected Task WriteData() - { - var streams = Enumerable.Range(0, Streams) - .Select(i => string.Format("bank_account_it{0}-{1}", GetIterationCode(), i)).ToArray(); - var slices = Split(streams, 3); - - var w1 = Write(WriteMode.SingleEventAtTime, slices[0], EventsPerStream, CreateBankEvent); - var w2 = Write(WriteMode.Bucket, slices[1], EventsPerStream, CreateBankEvent); - var w3 = Write(WriteMode.Transactional, slices[2], EventsPerStream, CreateBankEvent); - - var task = Task.Factory.ContinueWhenAll(new[] { w1, w2, w3 }, Task.WaitAll); - return task.ContinueWith(x => Log.Information("Data written for iteration {iteration}.", GetIterationCode())); - } - - protected string CreateCountItem() - { - var projectionManager = GetProjectionsManager(); - - string countItemsProjectionName = string.Format("CountItems_it{0}", GetIterationCode()); - string countItemsProjection = string.Format(@" - fromCategory('bank_account_it{0}').when({{ - $init: function() {{ return {{count:0}}; }}, - AccountCredited: function (state, event) {{ - state.count += 1; - }}, - AccountDebited: function (state, event) {{ - state.count += 1; - }}, - AccountCheckPoint: function (state, event) {{ - state.count += 1; - }} - }}) -", GetIterationCode()); - - projectionManager.CreateContinuousAsync(countItemsProjectionName, countItemsProjection, AdminCredentials) - .Wait(); - return countItemsProjectionName; - } - - protected string CreateSumCheckForBankAccount0() - { - string countItemsProjectionName = string.Format("CheckSumsInAccounts_it{0}", GetIterationCode()); - string countItemsProjection = string.Format(@" - fromStream('bank_account_it{0}-0').when({{ - $init: function() {{ - return {{credited:0, credsum:'', debited:0, debsum:''}}; - }}, - AccountCredited: function (state, event) {{ - state.credited += event.body.creditedAmount; - /*state.credsum += '#' + event.sequenceNumber + ':' + event.body.creditedAmount + ';'*/ - }}, - AccountDebited: function (state, event) {{ - state.debited += event.body.debitedAmount; - /*state.debsum += '#' + event.sequenceNumber + ':' + event.body.debitedAmount + ';'*/ - }}, - AccountCheckPoint: function(state, event) {{ - if (state.credited != event.body.creditedAmount) {{ - throw JSON.stringify({{ - message: 'Credited amount is incorrect. ', - expected: event.body.creditedAmount, - actual: state.credited, - stream: event.streamId, - ver: event.sequenceNumber, - details: state.credsum }}); - }} - if (state.debited != event.body.debitedAmount) {{ - throw JSON.stringify({{ - message: 'Debited amount is incorrect. ', - expected: event.body.debitedAmount, - actual: state.debited, - stream: event.streamId, - ver: event.sequenceNumber, - details: state.debsum }}); - }} - state.success=event.sequenceNumber; - }} - }}) -", GetIterationCode()); - - GetProjectionsManager() - .CreateContinuousAsync(countItemsProjectionName, countItemsProjection, AdminCredentials).Wait(); - - return countItemsProjectionName; - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsScenarioBase.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsScenarioBase.cs deleted file mode 100644 index 73c73536e5..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/ProjectionsScenarioBase.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Threading; -using EventStore.TestClient.Commands; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal abstract class ProjectionsScenarioBase : ScenarioBase -{ - protected ProjectionsScenarioBase(Action directSendOverTcp, int maxConcurrentRequests, - int connections, int streams, int eventsPerStream, int streamDeleteStep, string dbParentPath, - NodeConnectionInfo customNode) - : base(directSendOverTcp, maxConcurrentRequests, connections, streams, eventsPerStream, streamDeleteStep, - dbParentPath, customNode) - { - } - - protected bool CheckProjectionState(string projectionName, string key, Func checkValue) - { - var state = GetProjectionState(projectionName); - string value; - return state != null && state.Count > 0 && state.TryGetValue(key, out value) && checkValue(value); - } - - protected T GetProjectionStateValue(string projectionName, string key, Func convert, - T defaultValue) - { - var result = defaultValue; - - var state = GetProjectionState(projectionName); - string value; - if (state != null && state.Count > 0 && state.TryGetValue(key, out value)) - { - result = convert(value); - } - - return result; - } - - protected bool GetProjectionIsRunning(string projectionName) - { - var dic = GetProjectionStatistics(projectionName); - - string value; - var isRunning = dic != null && dic.TryGetValue("status", out value) && value == "Running"; - - return isRunning; - } - - protected long GetProjectionPosition(string projectionName) - { - var dic = GetProjectionStatistics(projectionName); - - long result = -1; - - string value; - if (dic != null && dic.TryGetValue("position", out value)) - { - if (!string.IsNullOrWhiteSpace(value)) - { - var subpositions = value.Split(':'); - var positionString = subpositions.Length == 2 ? subpositions[1] : value; - result = long.Parse(positionString); - } - else - { - result = -1; - } - - return result; - } - - return result; - } - - protected bool GetProjectionIsFaulted(string projectionName, out string reason) - { - var dic = GetProjectionStatistics(projectionName); - - string status; - var isFaulted = dic != null && dic.TryGetValue("status", out status) && status.StartsWith("Faulted"); - - if (isFaulted) - { - dic.TryGetValue("stateReason", out reason); - } - else - { - reason = null; - } - - return isFaulted; - } - - private Dictionary GetProjectionStatistics(string projectionName) - { - string rawState; - try - { - rawState = GetProjectionsManager().GetStatisticsAsync(projectionName, AdminCredentials).Result; - } - catch (Exception ex) - { - Log.Information(ex, "Failed to read projection statistics. Will continue."); - rawState = null; - } - - Log.Information("Raw {projection} stats: {rawState}", projectionName, rawState); - - if (string.IsNullOrEmpty(rawState)) - { - return null; - } - - if (rawState == "*** UNKNOWN ***") - { - return null; - } - - var start = rawState.IndexOf('['); - var end = rawState.IndexOf(']'); - - if (start == -1 || end == -1) - { - return null; - } - - var statDic = rawState.Substring(start + 1, end - start - 1); - - var state = TestClientJson.From>(statDic); - return state; - } - - private Dictionary GetProjectionState(string projectionName) - { - var rawState = GetProjectionStateSafe(projectionName); - - Log.Information("Raw {projection} state: {rawState}", projectionName, rawState); - - if (string.IsNullOrEmpty(rawState)) - { - return null; - } - - if (rawState == "*** UNKNOWN ***") - { - return null; - } - - var state = TestClientJson.From>(rawState); - return state; - } - - private string GetProjectionStateSafe(string projectionName) - { - string rawState; - try - { - rawState = GetProjectionsManager().GetStateAsync(projectionName, AdminCredentials).Result; - } - catch (Exception ex) - { - rawState = null; - Log.Information(ex, "Failed to get projection state"); - } - - return rawState; - } - - protected void EnableProjectionByCategory() - { - const string byCategoryProjection = "$by_category"; - - var retryCount = 0; - Exception exception = null; - while (retryCount < 5) - { - Thread.Sleep(250 * (retryCount * retryCount)); - try - { - var isRunning = GetProjectionIsRunning(byCategoryProjection); - - if (!isRunning) - { - Log.Debug("Enable *{projection}* projection", byCategoryProjection); - GetProjectionsManager().EnableAsync(byCategoryProjection, AdminCredentials).Wait(); - } - else - { - Log.Debug("Already enabled *{projection}* projection", byCategoryProjection); - } - - exception = null; - break; - } - catch (Exception ex) - { - exception = new ApplicationException("Failed to enable by_category.", ex); - Log.Error(ex, "Failed to enable *$by_category* projection, retry #{retryCount}.", - retryCount); - } - - retryCount += 1; - } - - if (exception != null) - { - throw exception; - } - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/ScenarioBase.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/ScenarioBase.cs deleted file mode 100644 index 0c32302ffd..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/ScenarioBase.cs +++ /dev/null @@ -1,705 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.ClientAPI.Projections; -using EventStore.ClientAPI.SystemData; -using EventStore.Core.Services; -using ConsoleLogger = EventStore.ClientAPI.Common.Log.ConsoleLogger; -using ILogger = Serilog.ILogger; -using TcpCommand = EventStore.Core.Services.Transport.Tcp.TcpCommand; -using TcpPackage = EventStore.Core.Services.Transport.Tcp.TcpPackage; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal abstract class ScenarioBase : IScenario -{ - protected static readonly ILogger Log = Serilog.Log.ForContext(); - - protected static readonly ClientAPI.ILogger ApiLogger = - new ClientApiLoggerBridge(Serilog.Log.ForContext(Serilog.Core.Constants.SourceContextPropertyName, - "client-api")); - - protected readonly UserCredentials AdminCredentials = - new UserCredentials(SystemUsers.Admin, SystemUsers.DefaultAdminPassword); - - protected readonly Action DirectSendOverTcp; - protected readonly int MaxConcurrentRequests; - protected readonly int Connections; - protected readonly int Streams; - protected readonly int EventsPerStream; - protected readonly int StreamDeleteStep; - - private readonly NodeConnectionInfo _customNodeConnection; - private readonly NodeConnectionInfo _nodeConnection; - - private readonly HashSet _startedNodesProcIds; - - private readonly string _dbPath; - - private readonly Dictionary, Task>> _writeHandlers; - private readonly IEventStoreConnection[] _connections; - private int _nextConnectionNum = -1; - private readonly ProjectionsManager _projectionsManager; - - protected virtual TimeSpan StartupWaitInterval - { - get { return TimeSpan.FromSeconds(12); } - } - - protected ScenarioBase(Action directSendOverTcp, - int maxConcurrentRequests, - int connections, - int streams, - int eventsPerStream, - int streamDeleteStep, - string dbParentPath, - NodeConnectionInfo customNodeConnection) - { - DirectSendOverTcp = directSendOverTcp; - MaxConcurrentRequests = maxConcurrentRequests; - Connections = connections; - Streams = streams; - EventsPerStream = eventsPerStream; - StreamDeleteStep = streamDeleteStep; - _customNodeConnection = customNodeConnection; - - _startedNodesProcIds = new HashSet(); - - var ipAddress = IPAddress.Loopback; - - if (_customNodeConnection != null) - { - _nodeConnection = _customNodeConnection; - _dbPath = null; - } - else - { - _dbPath = CreateNewDbPath(dbParentPath); - _nodeConnection = new NodeConnectionInfo(ipAddress, - PortsHelper.GetAvailablePort(ipAddress), - PortsHelper.GetAvailablePort(ipAddress)); - } - - _connections = new IEventStoreConnection[connections]; - - Log.Information("Projection manager points to {nodeConnection}.", _nodeConnection); - _projectionsManager = new ProjectionsManager(new ConsoleLogger(), - new IPEndPoint(_nodeConnection.IpAddress, _nodeConnection.HttpPort), TimeSpan.FromMilliseconds(5000)); - - _writeHandlers = new Dictionary, Task>> { - {WriteMode.SingleEventAtTime, WriteSingleEventAtTime}, - {WriteMode.Bucket, WriteBucketOfEventsAtTime}, - {WriteMode.Transactional, WriteEventsInTransactionalWay} - }; - } - - private static string CreateNewDbPath(string dbParentPath) - { - var dbParent = dbParentPath ?? Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - var dataFolder = Path.Combine(dbParent, "data"); - var idx = 0; - var dbPath = Path.Combine(dataFolder, string.Format("es_{0}", idx)); - - while (Directory.Exists(dbPath)) - { - idx += 1; - dbPath = Path.Combine(dataFolder, string.Format("es_{0}", idx)); - } - - return dbPath; - } - - protected IEventStoreConnection GetConnection() - { - var connectionNum = (int)(((uint)Interlocked.Increment(ref _nextConnectionNum)) % Connections); - return _connections[connectionNum]; - } - - protected ProjectionsManager GetProjectionsManager() - { - return _projectionsManager; - } - - public void Run() - { - const int maxReconnections = 200; - const int maxOperationRetries = 200; - - for (int i = 0; i < Connections; ++i) - { - _connections[i] = EventStoreConnection.Create( - ConnectionSettings.Create() - .UseCustomLogger(ApiLogger) - .LimitConcurrentOperationsTo(MaxConcurrentRequests) - .LimitRetriesForOperationTo(maxReconnections) - .LimitReconnectionsTo(maxOperationRetries) - .FailOnNoServerResponse(), - new Uri(string.Format("tcp://admin:changeit@{0}:{1}", _nodeConnection.IpAddress, - _nodeConnection.TcpPort)), - string.Format("ESConn-{0}", i)); - _connections[i].Closed += (s, e) => - Log.Debug("[SCENARIO] {connection} closed.", e.Connection.ConnectionName); - _connections[i].Connected += (s, e) => - Log.Debug("[SCENARIO] {connection} connected to [{remoteEndPoint}].", e.Connection.ConnectionName, - e.RemoteEndPoint); - _connections[i].Disconnected += (s, e) => - Log.Debug("[SCENARIO] {connection} disconnected from [{remoteEndPoint}].", - e.Connection.ConnectionName, e.RemoteEndPoint); - _connections[i].Reconnecting += (s, e) => - Log.Debug("[SCENARIO] {connection} reconnecting.", e.Connection.ConnectionName); - _connections[i].ErrorOccurred += (s, e) => Log.Debug(e.Exception, - "[SCENARIO] {connection} error occurred.", e.Connection.ConnectionName); - _connections[i].ConnectAsync().Wait(); - } - - RunInternal(); - } - - protected abstract void RunInternal(); - - public void Clean() - { - CloseConnections(); - KillStartedNodes(); - DeleteDatabase(); - } - - private void DeleteDatabase() - { - try - { - if (_dbPath != null) - { - Log.Information("Deleting {dbPath}...", _dbPath); - Directory.Delete(_dbPath, true); - Log.Information("Deleted {dbPath}", _dbPath); - } - } - catch (IOException ex) - { - Log.Error(ex, "Failed to delete dir {dbPath}, IOException was raised", _dbPath); - } - catch (UnauthorizedAccessException ex) - { - Log.Error(ex, "Failed to delete dir {dbPath}, UnauthorizedAccessException was raised", - _dbPath); - } - } - - protected T[][] Split(IEnumerable sequence, int parts) - { - return sequence.Select((x, i) => new { GroupNum = i % parts, Item = x }) - .GroupBy(x => x.GroupNum, y => y.Item) - .Select(x => x.ToArray()) - .ToArray(); - } - - - protected Task Write(WriteMode mode, string[] streams, int eventsPerStream) - { - Func createEvent = TestEvent.NewTestEvent; - return Write(mode, streams, eventsPerStream, createEvent); - } - - protected Task Write(WriteMode mode, string[] streams, int eventsPerStream, Func createEvent) - { - Log.Information( - "Writing. Mode : {mode,-15} Streams : {streamsLength,-10} Events per stream : {eventsPerStream,-10}", - mode, - streams.Length, - eventsPerStream); - - Func, Task> handler; - if (!_writeHandlers.TryGetValue(mode, out handler)) - { - throw new ArgumentOutOfRangeException("mode"); - } - - var tasks = new List(); - for (var i = 0; i < streams.Length; i++) - { - //Console.WriteLine("WRITING TO {0}", streams[i]); - tasks.Add(handler(streams[i], eventsPerStream, createEvent)); - } - - return Task.Factory.ContinueWhenAll(tasks.ToArray(), tsks => - { - Task.WaitAll(tsks); - Log.Information( - "Finished writing. Mode : {mode,-15} Streams : {streamsLength,-10} Events per stream : {eventsPerStream,-10}", - mode, - streams.Length, - eventsPerStream); - }); - } - - protected void DeleteStreams(IEnumerable streams) - { - Log.Information("Deleting streams..."); - var store = GetConnection(); - - var tasks = new List(); - foreach (var stream in streams) - { - var s = stream; - Log.Information("Deleting stream {stream}...", stream); - var task = store.DeleteStreamAsync(stream, (EventsPerStream - 1), hardDelete: true) - .ContinueWith(x => Log.Information("Stream {stream} successfully deleted", s)); - - tasks.Add(task); - } - - Task.WaitAll(tasks.ToArray()); - - Log.Information("All streams successfully deleted"); - } - - protected Task CheckStreamsDeleted(IEnumerable streams) - { - Log.Information("Verifying streams are deleted..."); - - var store = GetConnection(); - var tasks = new List(); - - foreach (var stream in streams) - { - var s = stream; - var task = store.ReadStreamEventsForwardAsync(stream, 0, 1, resolveLinkTos: false).ContinueWith(t => - { - if (t.Result.Status != SliceReadStatus.StreamDeleted) - { - throw new Exception(string.Format("Stream '{0}' is not deleted, but should be!", s)); - } - }); - - tasks.Add(task); - } - - return Task.Factory.ContinueWhenAll(tasks.ToArray(), tsks => - { - Task.WaitAll(tsks); - Log.Information("Stream deletion verification succeeded."); - }); - } - - protected Task Read(string[] streams, int @from, int count) - { - if (streams.Length == 0) - { - throw new Exception("Streams shouldn't be empty."); - } - - Log.Information("Reading [{streams}]\nfrom {from,-10} count {count,-10}", string.Join(",", streams), from, count); - - var tasks = new List(); - - for (int i = 0; i < streams.Length; i++) - { - var task = ReadStream(streams[i], from, count); - tasks.Add(task); - } - - return Task.Factory.ContinueWhenAll(tasks.ToArray(), tsks => - { - Task.WaitAll(tsks); - Log.Information("Done reading [{streams}]", string.Join(",", streams)); - }); - } - - private bool TryGetPathToMono(out string pathToMono) - { - const string monopathVariable = "EVENTSTORE_MONOPATH"; - pathToMono = Environment.GetEnvironmentVariable(monopathVariable); - return !string.IsNullOrEmpty(pathToMono); - } - - protected int StartNode() - { - int processId = -1; - if (_customNodeConnection == null) - { - processId = StartNewNode(); - } - - return processId; - } - - private int StartNewNode() - { - var clientFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - //../../EventStore.ClusterNode/net471/EventStore.ClusterNode.exe - var clusterNodeFolder = System.IO.Directory.GetParent(System.IO.Directory.GetParent(clientFolder).FullName) - + "/EventStore.ClusterNode/net471/"; - string fileName; - string argumentsHead; - - string pathToMono; - if (TryGetPathToMono(out pathToMono)) - { - Log.Information("Mono at {pathToMono} will be used.", pathToMono); - fileName = pathToMono; - argumentsHead = string.Format("--debug --gc=sgen {0}", - Path.Combine(clusterNodeFolder, "EventStore.ClusterNode.exe")); - } - else - { - fileName = Path.Combine(clusterNodeFolder, "EventStore.ClusterNode.exe"); - argumentsHead = ""; - } - - var arguments = string.Format( - "{0} --run-projections=all --ext-ip {1} --ext-tcp-port {2} --ext-http-port {3} --db {4}", - argumentsHead, - _nodeConnection.IpAddress, - _nodeConnection.TcpPort, - _nodeConnection.HttpPort, - _dbPath); - - Log.Information("Starting [{fileName} {arguments}]...", fileName, arguments); - - var startInfo = new ProcessStartInfo(fileName, arguments); - - var nodeProcess = Process.Start(startInfo); - if (nodeProcess == null || nodeProcess.HasExited) - { - throw new ApplicationException(string.Format("Process was not started [{0} {1}].", fileName, - arguments)); - } - - Thread.Sleep(3000); - Process tmp; - var running = TryGetProcessById(nodeProcess.Id, out tmp); - if (!running || tmp.HasExited) - { - throw new ApplicationException(string.Format("Process was not started [{0} {1}].", fileName, - arguments)); - } - - _startedNodesProcIds.Add(nodeProcess.Id); - - Log.Information("Started node with process id {id}", nodeProcess.Id); - - Thread.Sleep(StartupWaitInterval); - Log.Information("Started [{fileName} {arguments}]", fileName, arguments); - - return nodeProcess.Id; - } - - private bool TryGetProcessById(int processId, out Process process) - { - process = null; - - try - { - process = Process.GetProcessById(processId); - } - catch (ArgumentException) - { - return false; - } - catch (InvalidOperationException) - { - return false; - } - - return true; - } - - protected void KillNode(int processId) - { - if (processId != -1) - { - KillStartedNode(processId); - } - else - { - Log.Information("Skip killing, procId -1"); - } - } - - private void KillStartedNode(int processId) - { - Log.Information("Killing {processId}...", processId); - - Process process; - if (TryGetProcessById(processId, out process)) - { - process.Kill(); - - var waitCount = 200; - while (!process.HasExited && waitCount > 0) - { - Thread.Sleep(250); - waitCount -= 1; - } - - if (process.HasExited) - { - _startedNodesProcIds.Remove(processId); - - PortsHelper.ReturnPort(_nodeConnection.TcpPort); - PortsHelper.ReturnPort(_nodeConnection.HttpPort); - - Log.Information("Killed process {processId}, wait a bit.", processId); - Thread.Sleep(1000); // wait for system to release port used by HttpListener. - } - else - { - if (TryGetProcessById(processId, out _)) - { - Log.Error( - "Process {processId} did not report about exit in time and is still present in processes list.", - processId); - } - else - { - Log.Information("Process {processId} did not report about exit in time but is not found again.", - processId); - } - } - } - else - { - Log.Error("Process {processId} was not found to be killed.", processId); - } - } - - public void Dispose() - { - CloseConnections(); - Thread.Sleep(2 * 1000); - KillStartedNodes(); - } - - private void CloseConnections() - { - for (int i = 0; i < _connections.Length; ++i) - { - _connections[i].Close(); - } - } - - private void KillStartedNodes() - { - Log.Information("Killing remaining nodes..."); - try - { - _startedNodesProcIds.ToList().ForEach(KillNode); - } - catch (Exception ex) - { - Log.Error("Failed to kill started nodes: {e}.", ex.Message); - } - } - - protected void Scavenge() - { - Log.Information("Send scavenge command..."); - var package = new TcpPackage(TcpCommand.ScavengeDatabase, Guid.NewGuid(), null).AsByteArray(); - DirectSendOverTcp(new IPEndPoint(_nodeConnection.IpAddress, _nodeConnection.TcpPort), package); - Log.Information("Scavenge command was sent."); - } - - private Task WriteSingleEventAtTime(string stream, int events, Func createEvent) - { - var resSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - - Log.Information("Starting to write {events} events to [{stream}]", events, stream); - var store = GetConnection(); - int eventVersion = 0; - - Action fail = prevTask => - { - Log.Information("WriteSingleEventAtTime for stream {stream} failed.", stream); - resSource.SetException(prevTask.Exception); - }; - - Action writeSingleEvent = null; - writeSingleEvent = _ => - { - if (eventVersion == events) - { - Log.Information("Wrote {events} events to [{stream}]", events, stream); - resSource.SetResult(null); - return; - } - - var writeTask = store.AppendToStreamAsync(stream, - eventVersion - 1, - new[] { createEvent(eventVersion) }); - - eventVersion += 1; - - writeTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - writeTask.ContinueWith(writeSingleEvent, TaskContinuationOptions.OnlyOnRanToCompletion); - }; - - writeSingleEvent(null); - - return resSource.Task; - } - - private Task WriteBucketOfEventsAtTime(string stream, int eventCount, Func createEvent) - { - const int bucketSize = 25; - Log.Information("Starting to write {eventCount} events to [{stream}] ({bucketSize} events at once)", eventCount, - stream, bucketSize); - - var resSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var store = GetConnection(); - int writtenCount = 0; - - Action fail = prevTask => - { - Log.Information("WriteBucketOfEventsAtTime for stream {stream} failed.", stream); - resSource.SetException(prevTask.Exception); - }; - - Action writeBatch = null; - writeBatch = _ => - { - if (writtenCount == eventCount) - { - Log.Information("Wrote {eventCount} events to [{stream}] ({bucketSize} events at once)", eventCount, - stream, bucketSize); - resSource.SetResult(null); - return; - } - - var startIndex = writtenCount; - var endIndex = Math.Min(eventCount, startIndex + bucketSize); - var events = Enumerable.Range(startIndex, endIndex - startIndex).Select(createEvent).ToArray(); - - writtenCount = endIndex; - - var expectedVersion = startIndex - 1; - var writeTask = store.AppendToStreamAsync(stream, expectedVersion, events); - - writeTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - writeTask.ContinueWith(writeBatch, TaskContinuationOptions.OnlyOnRanToCompletion); - }; - - writeBatch(null); - - return resSource.Task; - } - - private Task WriteEventsInTransactionalWay(string stream, int eventCount, Func createEvent) - { - Log.Information("Starting to write {eventCount} events to [{stream}] (in single transaction)", eventCount, stream); - - var resSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var store = GetConnection(); - - Action fail = prevTask => - { - Log.Information("WriteEventsInTransactionalWay for stream {stream} failed.", stream); - resSource.SetException(prevTask.Exception); - }; - - int writtenCount = 0; - EventStoreTransaction transaction = null; - - Action writeTransactionEvent = null; - writeTransactionEvent = prevTask => - { - if (writtenCount == eventCount) - { - var commitTask = transaction.CommitAsync(); - commitTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - commitTask.ContinueWith(t => - { - Log.Information("Wrote {eventCount} events to [{stream}] (in single transaction)", eventCount, stream); - resSource.SetResult(null); - }, TaskContinuationOptions.OnlyOnRanToCompletion); - return; - } - - var writeTask = transaction.WriteAsync(createEvent(writtenCount)); - - writtenCount += 1; - - writeTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - writeTask.ContinueWith(writeTransactionEvent, TaskContinuationOptions.OnlyOnRanToCompletion); - }; - - var startTask = store.StartTransactionAsync(stream, -1); - startTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - startTask.ContinueWith(t => - { - transaction = t.Result; - writeTransactionEvent(t); - }, TaskContinuationOptions.OnlyOnRanToCompletion); - - return resSource.Task; - } - - private Task ReadStream(string stream, int from, int count) - { - Log.Information("Reading [{stream}] from {from,-10} count {count,-10}", stream, from, count); - var resSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var store = GetConnection(); - - Action fail = prevTask => - { - Log.Information("ReadStream for stream {stream} failed.", stream); - resSource.SetException(prevTask.Exception); - }; - - var readTask = store.ReadStreamEventsForwardAsync(stream, @from, count, resolveLinkTos: false); - readTask.ContinueWith(fail, TaskContinuationOptions.OnlyOnFaulted); - readTask.ContinueWith(t => - { - try - { - var slice = t.Result; - if (slice?.Events == null || slice.Events.Length != count) - { - throw new Exception(string.Format( - "Tried to read {0} events from event number {1} from stream '{2}' but failed. Reason: {3}.", - count, - from, - stream, - slice == null ? "slice == null" - : slice.Events == null ? "slive.Events == null" - : slice.Events.Length != count ? string.Format("Expected count: {0}, actual count: {1}.", - count, slice.Events.Length) - : "WAT?!?")); - } - - for (int i = 0; i < count; ++i) - { - var evnt = slice.Events[i].Event; - if (evnt.EventNumber != i + from) - { - throw new Exception(string.Format( - "Received event with wrong event number. Expected: {0}, actual: {1}.\nEvent: {2}.", - from + i, - evnt.EventNumber, - evnt)); - } - - TestEvent.VerifyIfMatched(evnt); - } - - Log.Information("Done reading [{stream}] from {from,-10} count {count,-10}", stream, from, count); - resSource.SetResult(null); - } - catch (Exception exc) - { - Log.Information("ReadStream for stream {stream} failed.", stream); - resSource.SetException(exc); - } - }, TaskContinuationOptions.OnlyOnRanToCompletion); - - return resSource.Task; - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/TestEvent.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/TestEvent.cs deleted file mode 100644 index daba21602e..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/TestEvent.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Text; -using EventStore.ClientAPI; -using EventStore.Common.Utils; - -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal class TestEvent -{ - public static EventData NewTestEvent(int index) - { - var subIndex = (index % 50); - var type = "TestEvent-" + subIndex.ToString(); - var body = new string('#', 1 + subIndex * subIndex); - var encodedData = Helper.UTF8NoBom.GetBytes(string.Format("{0}-{1}-{2}", index, body.Length, body)); - - return new EventData(Guid.NewGuid(), type, false, encodedData, new byte[0]); - } - - public static void VerifyIfMatched(RecordedEvent evnt) - { - if (evnt.EventType.StartsWith("TestEvent")) - { - var data = Common.Utils.Helper.UTF8NoBom.GetString(evnt.Data); - var atoms = data.Split('-'); - if (atoms.Length != 3) - { - throw new ApplicationException(string.Format("Invalid TestEvent object: currupted data format: {0}", - RecordDetailsString(evnt))); - } - - var expectedLength = int.Parse(atoms[1]); - if (expectedLength != atoms[2].Length) - { - throw new ApplicationException(string.Format( - "Invalid TestEvent object: not expected data length: {0}", - RecordDetailsString(evnt))); - } - - if (new string('#', expectedLength) != atoms[2]) - { - throw new ApplicationException(string.Format("Invalid TestEvent object: currupted data: {0}", - RecordDetailsString(evnt))); - } - } - } - - private static string RecordDetailsString(RecordedEvent evnt) - { - var data = Common.Utils.Helper.UTF8NoBom.GetString(evnt.Data); - return string.Format("[stream:{0}; eventNumber:{1}; type:{2}; data:{3}]", - evnt.EventStreamId, - evnt.EventNumber, - evnt.EventType, - data.Length > 12 ? (data.Substring(0, 12) + "...") : data); - } -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenarios/WriteMode.cs b/src/EventStore.TestClient/Commands/RunTestScenarios/WriteMode.cs deleted file mode 100644 index 7f06a592e3..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenarios/WriteMode.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace EventStore.TestClient.Commands.RunTestScenarios; - -internal enum WriteMode -{ - SingleEventAtTime, - Bucket, - Transactional -} diff --git a/src/EventStore.TestClient/Commands/RunTestScenariosProcessor.cs b/src/EventStore.TestClient/Commands/RunTestScenariosProcessor.cs deleted file mode 100644 index 98fa09860b..0000000000 --- a/src/EventStore.TestClient/Commands/RunTestScenariosProcessor.cs +++ /dev/null @@ -1,280 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.TestClient.Commands.RunTestScenarios; -using EventStore.Transport.Tcp; -using ILogger = Serilog.ILogger; - -namespace EventStore.TestClient.Commands; - -internal class RunTestScenariosProcessor : ICmdProcessor -{ - private static readonly ILogger Log = Serilog.Log.ForContext(); - private const string AllScenariosFlag = "ALL"; - - public string Keyword - { - get { return "RT"; } - } - - public string Usage - { - get - { - const string usage = " " + - "\n " + - "\n " + - "\n " + - "\n " + - "\n" + - "\n" + - "\n"; - - return usage; - } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - if (args.Length != 0 && false == (args.Length == 7 || args.Length == 8)) - { - return false; - } - - var maxConcurrentRequests = 20; - var connections = 10; - var streams = 100; - var eventsPerStream = 400; - var streamDeleteStep = 7; - var scenarioName = ""; - var executionPeriodMinutes = 2; - string dbParentPath = null; - NodeConnectionInfo customNode = null; - - { - if (args.Length == 9) - { - throw new ArgumentException( - "Not compatible arguments, only one of or can be specified."); - } - - if (args.Length == 8) - { - IPAddress ip; - int tcpPort; - int httpPort; - - var atoms = args[7].Split(':'); - if (atoms.Length == 3 - && IPAddress.TryParse(atoms[0], out ip) - && int.TryParse(atoms[1], out tcpPort) - && int.TryParse(atoms[2], out httpPort)) - { - customNode = new NodeConnectionInfo(ip, tcpPort, httpPort); - - args = CutLastArgument(args); - } - } - - if (args.Length == 7 || args.Length == 8) - { - try - { - maxConcurrentRequests = int.Parse(args[0]); - connections = int.Parse(args[1]); - streams = int.Parse(args[2]); - eventsPerStream = int.Parse(args[3]); - streamDeleteStep = int.Parse(args[4]); - scenarioName = args[5]; - executionPeriodMinutes = int.Parse(args[6]); - - if (args.Length == 8) - { - dbParentPath = args[7]; - } - else - { - var envDbPath = Environment.GetEnvironmentVariable("EVENTSTORE_DATABASEPATH"); - if (!string.IsNullOrEmpty(envDbPath)) - { - dbParentPath = envDbPath; - } - } - } - catch (Exception e) - { - Log.Error("Invalid arguments ({e})", e.Message); - return false; - } - } - } - - context.IsAsync(); - - Log.Information("\n---" + - "\nRunning scenario {scenario} using {connections} connections with {maxConcurrentRequests} max concurrent requests," + - "\nfor {streams} streams {eventsPerStream} events each deleting every {streamDeleteStep}th stream. " + - "\nExecution period {executionPeriod} minutes. " + - "\nDatabase path {dbParentPath};" + - "\nCustom Node {customNode};" + - "\n---", - scenarioName, - connections, - maxConcurrentRequests, - streams, - eventsPerStream, - streamDeleteStep, - executionPeriodMinutes, - dbParentPath, - customNode); - - var directTcpSender = CreateDirectTcpSender(context); - var allScenarios = new IScenario[] { - new LoopingScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - TimeSpan.FromMinutes(executionPeriodMinutes), - dbParentPath, - customNode), - new ProjectionsKillScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - dbParentPath, - customNode), - new ProjForeachForcedCommonNameScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - TimeSpan.FromMinutes(executionPeriodMinutes), - dbParentPath, - customNode), - new ProjForeachForcedCommonNameNoRestartScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - TimeSpan.FromMinutes(executionPeriodMinutes), - dbParentPath, - customNode), - new LoopingProjTranWriteScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - TimeSpan.FromMinutes(executionPeriodMinutes), - dbParentPath, - customNode), - new LoopingProjectionKillScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - TimeSpan.FromMinutes(executionPeriodMinutes), - dbParentPath, - customNode), - new MassProjectionsScenario(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - dbParentPath, - customNode), - new ProjectionWrongTagCheck(directTcpSender, - maxConcurrentRequests, - connections, - streams, - eventsPerStream, - streamDeleteStep, - TimeSpan.FromMinutes(executionPeriodMinutes), - dbParentPath, - customNode), - }; - - Log.Information("Found scenarios {scenariosCount} total :\n{scenarios}.", allScenarios.Length, - allScenarios.Aggregate(new StringBuilder(), - (sb, s) => sb.AppendFormat("{0}, ", s.GetType().Name))); - var scenarios = allScenarios.Where(x => scenarioName == AllScenariosFlag - || x.GetType().Name.Equals(scenarioName, - StringComparison.InvariantCultureIgnoreCase)) - .ToArray(); - - Log.Information("Running test scenarios ({scenarios} total)...", scenarios.Length); - - foreach (var scenario in scenarios) - { - using (scenario) - { - try - { - Log.Information("Run scenario {type}", scenario.GetType().Name); - scenario.Run(); - scenario.Clean(); - Log.Information("Scenario run successfully"); - } - catch (Exception e) - { - context.Fail(e); - } - } - } - - Log.Information("Finished running test scenarios"); - - if (context.ExitCode == 0) - { - context.Success(); - } - - return true; - } - - private static string[] CutLastArgument(string[] args) - { - var cutArgs = new string[7]; - Array.Copy(args, cutArgs, 7); - return cutArgs; - } - - private Action CreateDirectTcpSender(CommandProcessorContext context) - { - const int timeoutMilliseconds = 4000; - - Action sender = (tcpEndPoint, bytes) => - { - var sent = new AutoResetEvent(false); - - Action, TcpPackage> handlePackage = (_, __) => { }; - Action> established = connection => - { - connection.EnqueueSend(bytes); - connection.Close(); - sent.Set(); - }; - Action, SocketError> closed = (_, __) => sent.Set(); - - context._tcpTestClient.CreateTcpConnection(context, handlePackage, established, closed, false, tcpEndPoint); - if (!sent.WaitOne(timeoutMilliseconds)) - { - throw new ApplicationException("Connection to server was not closed in time."); - } - }; - - return sender; - } -} diff --git a/src/EventStore.TestClient/Commands/ScavengeProcessor.cs b/src/EventStore.TestClient/Commands/ScavengeProcessor.cs deleted file mode 100644 index 28ca0af213..0000000000 --- a/src/EventStore.TestClient/Commands/ScavengeProcessor.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Net.Sockets; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class ScavengeProcessor : ICmdProcessor -{ - public string Usage - { - get { return Keyword; } - } - - public string Keyword - { - get { return "SCAVENGE"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - var package = new TcpPackage(TcpCommand.ScavengeDatabase, Guid.NewGuid(), null); - context.Log.Information("Sending SCAVENGE request..."); - - var connection = context._tcpTestClient.CreateTcpConnection( - context, - (conn, pkg) => { }, - null, - (typedConnection, error) => - { - if (error == SocketError.Success) - { - context.Success(); - } - else - { - context.Fail(); - } - }); - connection.EnqueueSend(package.AsByteArray()); - connection.Close("OK"); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/SubscribeToStreamProcessor.cs b/src/EventStore.TestClient/Commands/SubscribeToStreamProcessor.cs deleted file mode 100644 index aab311401b..0000000000 --- a/src/EventStore.TestClient/Commands/SubscribeToStreamProcessor.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using EventStore.Client.Messages; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class SubscribeToStreamProcessor : ICmdProcessor -{ - public string Usage - { - get { return "SUBSCR [ ... ]"; } - } - - public string Keyword - { - get { return "SUBSCR"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - context.IsAsync(); - - var streamByCorrId = new Dictionary(); - - var connection = context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => { }, - handlePackage: (conn, pkg) => - { - switch (pkg.Command) - { - case TcpCommand.SubscriptionConfirmation: - { - var dto = pkg.Data.Deserialize(); - context.Log.Information( - "Subscription to <{stream}> WAS CONFIRMED! Subscribed at {lastIndexedPosition} ({lastEventNumber})", - streamByCorrId[pkg.CorrelationId], dto.LastCommitPosition, dto.LastEventNumber); - break; - } - case TcpCommand.StreamEventAppeared: - { - var dto = pkg.Data.Deserialize(); - context.Log.Information("NEW EVENT:\n\n" - + "\tEventStreamId: {stream}\n" - + "\tEventNumber: {eventNumber}\n" - + "\tEventType: {eventType}\n" - + "\tData: {data}\n" - + "\tMetadata: {metadata}\n", - dto.Event.Event.EventStreamId, - dto.Event.Event.EventNumber, - dto.Event.Event.EventType, - Common.Utils.Helper.UTF8NoBom.GetString(dto.Event.Event.Data.ToByteArray()), - Common.Utils.Helper.UTF8NoBom.GetString(dto.Event.Event.Metadata.ToByteArray())); - break; - } - case TcpCommand.SubscriptionDropped: - { - pkg.Data.Deserialize(); - context.Log.Error("Subscription to <{stream}> WAS DROPPED!", - streamByCorrId[pkg.CorrelationId]); - break; - } - default: - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - break; - } - }, - connectionClosed: (c, error) => - { - if (error == SocketError.Success) - { - context.Success(); - } - else - { - context.Fail(); - } - }); - - if (args.Length == 0) - { - context.Log.Information("SUBSCRIBING TO ALL STREAMS..."); - var cmd = new SubscribeToStream(string.Empty, resolveLinkTos: false); - Guid correlationId = Guid.NewGuid(); - streamByCorrId[correlationId] = "$all"; - connection.EnqueueSend(new TcpPackage(TcpCommand.SubscribeToStream, correlationId, cmd.Serialize()) - .AsByteArray()); - } - else - { - foreach (var stream in args) - { - context.Log.Information("SUBSCRIBING TO STREAM <{stream}>...", stream); - var cmd = new SubscribeToStream(stream, resolveLinkTos: false); - var correlationId = Guid.NewGuid(); - streamByCorrId[correlationId] = stream; - connection.EnqueueSend(new TcpPackage(TcpCommand.SubscribeToStream, correlationId, cmd.Serialize()) - .AsByteArray()); - } - } - - context.WaitForCompletion(); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/SubscriptionStressTestProcessor.cs b/src/EventStore.TestClient/Commands/SubscriptionStressTestProcessor.cs deleted file mode 100644 index 4a347a6a05..0000000000 --- a/src/EventStore.TestClient/Commands/SubscriptionStressTestProcessor.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using EventStore.ClientAPI; -using EventStore.Common.Utils; - -namespace EventStore.TestClient.Commands; - -internal class SubscriptionStressTestProcessor : ICmdProcessor -{ - public string Usage - { - get { return "SST []"; } - } - - public string Keyword - { - get { return "SST"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int subscriptionCount = 5000; - - if (args.Length > 0) - { - if (args.Length > 1) - { - return false; - } - - subscriptionCount = MetricPrefixValue.ParseInt(args[0]); - } - - context.IsAsync(); - - var conn = EventStoreConnection.Create(ConnectionSettings.Create() - .UseCustomLogger(new ClientApiLoggerBridge(context.Log)) - .FailOnNoServerResponse() - /*.EnableVerboseLogging()*/, - new Uri($"tcp://{context._tcpTestClient.TcpEndpoint.GetHost()}:{context._tcpTestClient.TcpEndpoint.GetPort()}")); - conn.ConnectAsync().Wait(); - - long appearedCnt = 0; - var sw = Stopwatch.StartNew(); - for (int i = 0; i < subscriptionCount; ++i) - { - conn.SubscribeToStreamAsync( - string.Format("stream-{0}", i), - false, - (s, e) => - { - var c = Interlocked.Increment(ref appearedCnt); - if (c % 1000 == 0) - { - Console.Write('\''); - } - - if (c % 100000 == 0) - { - context.Log.Debug("Received total {events} events ({rate} per sec)...", c, - 100000.0 / sw.Elapsed.TotalSeconds); - sw.Restart(); - } - - return Task.CompletedTask; - }).Wait(); - } - - context.Log.Information("Subscribed to {subscriptionCount} streams...", subscriptionCount); - return true; - } -} diff --git a/src/EventStore.TestClient/Commands/TcpSanitazationCheckProcessor.cs b/src/EventStore.TestClient/Commands/TcpSanitazationCheckProcessor.cs deleted file mode 100644 index d65887161d..0000000000 --- a/src/EventStore.TestClient/Commands/TcpSanitazationCheckProcessor.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using EventStore.Common.Utils; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; -using ILogger = Serilog.ILogger; - -namespace EventStore.TestClient.Commands; - -internal class TcpSanitazationCheckProcessor : ICmdProcessor -{ - private static readonly ILogger Log = Serilog.Log.ForContext(); - - public string Keyword - { - get { return "CHKTCP"; } - } - - public string Usage - { - get { return Keyword; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - context.IsAsync(); - - var commandsToCheck = new[] { - (byte)TcpCommand.WriteEvents, - (byte)TcpCommand.TransactionStart, - (byte)TcpCommand.TransactionWrite, - (byte)TcpCommand.TransactionCommit, - (byte)TcpCommand.DeleteStream, - (byte)TcpCommand.ReadEvent, - (byte)TcpCommand.ReadStreamEventsForward, - (byte)TcpCommand.ReadStreamEventsBackward, - (byte)TcpCommand.ReadAllEventsForward, - (byte)TcpCommand.ReadAllEventsBackward, - (byte)TcpCommand.SubscribeToStream, - (byte)TcpCommand.UnsubscribeFromStream, - }; - - var packages = commandsToCheck.Select(c => - new TcpPackage((TcpCommand)c, Guid.NewGuid(), new byte[] { 0, 1, 0, 1 }).AsByteArray()) - .Union(new[] { - BitConverter.GetBytes(int.MaxValue).Union(new byte[] {1, 2, 3, 4}).ToArray(), - BitConverter.GetBytes(int.MinValue).Union(new byte[] {1, 2, 3, 4}).ToArray(), - BitConverter.GetBytes(0).Union(Enumerable.Range(0, 256).Select(x => (byte)x)).ToArray() - }); - - int step = 0; - foreach (var pkg in packages) - { - var established = new AutoResetEvent(false); - var dropped = new AutoResetEvent(false); - - if (step < commandsToCheck.Length) - { - Console.WriteLine("{0} Starting step {1} ({2}) {0}", new string('#', 20), step, - (TcpCommand)commandsToCheck[step]); - } - else - { - Console.WriteLine("{0} Starting step {1} (RANDOM BYTES) {0}", new string('#', 20), step); - } - - var connection = context._tcpTestClient.CreateTcpConnection( - context, - (conn, package) => - { - if (package.Command != TcpCommand.BadRequest) - { - context.Fail(null, string.Format("Bad request expected, got {0}!", package.Command)); - } - }, - conn => established.Set(), - (conn, err) => dropped.Set()); - - established.WaitOne(); - connection.EnqueueSend(pkg); - dropped.WaitOne(); - - if (step < commandsToCheck.Length) - { - Console.WriteLine("{0} Step {1} ({2}) Completed {0}", new string('#', 20), step, - (TcpCommand)commandsToCheck[step]); - } - else - { - Console.WriteLine("{0} Step {1} (RANDOM BYTES) Completed {0}", new string('#', 20), step); - } - - step++; - } - - Log.Information( - "Sent {packages} packages. {commandsToCheck} invalid dtos, {barFormattedPackages} bar formatted packages. Got {badRequests} BadRequests. Success", - packages.Count(), - commandsToCheck.Length, - packages.Count() - commandsToCheck.Length, - packages.Count()); - - Log.Information("Now sending raw bytes..."); - try - { - SendRaw(context._tcpTestClient.TcpEndpoint, BitConverter.GetBytes(int.MaxValue)); - SendRaw(context._tcpTestClient.TcpEndpoint, BitConverter.GetBytes(int.MinValue)); - - SendRaw(context._tcpTestClient.TcpEndpoint, BitConverter.GetBytes(double.MinValue)); - SendRaw(context._tcpTestClient.TcpEndpoint, BitConverter.GetBytes(double.MinValue)); - - SendRaw(context._tcpTestClient.TcpEndpoint, BitConverter.GetBytes(new Random().NextDouble())); - } - catch (Exception e) - { - context.Fail(e, "Raw bytes sent failed"); - return false; - } - - context.Success(); - return true; - } - - private void SendRaw(EndPoint endPoint, byte[] package) - { - using var client = new TcpClient(); - client.Connect(endPoint.ResolveDnsToIPAddress()); - using var stream = client.GetStream(); - stream.Write(package, 0, package.Length); - } -} diff --git a/src/EventStore.TestClient/Commands/TestClientJson.cs b/src/EventStore.TestClient/Commands/TestClientJson.cs deleted file mode 100644 index 068c9ee3a5..0000000000 --- a/src/EventStore.TestClient/Commands/TestClientJson.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; - -namespace EventStore.TestClient.Commands; - -internal static class TestClientJson -{ - private static readonly JsonSerializerSettings FromSettings = new() - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - DateParseHandling = DateParseHandling.None, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Include, - MissingMemberHandling = MissingMemberHandling.Ignore, - TypeNameHandling = TypeNameHandling.None, - MetadataPropertyHandling = MetadataPropertyHandling.Ignore, - Converters = [new StringEnumConverter()] - }; - - private static readonly JsonSerializerSettings ToSettings = new() - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - DateFormatHandling = DateFormatHandling.IsoDateFormat, - NullValueHandling = NullValueHandling.Include, - DefaultValueHandling = DefaultValueHandling.Include, - MissingMemberHandling = MissingMemberHandling.Ignore, - TypeNameHandling = TypeNameHandling.None, - Converters = [new StringEnumConverter()] - }; - - public static T From(string text) => - JsonConvert.DeserializeObject(text, FromSettings); - - public static string To(T value) => - value is null ? "" : JsonConvert.SerializeObject(value, Formatting.Indented, ToSettings); -} diff --git a/src/EventStore.TestClient/Commands/TransactionWriteProcessor.cs b/src/EventStore.TestClient/Commands/TransactionWriteProcessor.cs deleted file mode 100644 index 8da7210c81..0000000000 --- a/src/EventStore.TestClient/Commands/TransactionWriteProcessor.cs +++ /dev/null @@ -1,199 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net.Sockets; -using System.Text; -using EventStore.Client.Messages; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; -using OperationResult = EventStore.Client.Messages.OperationResult; - -namespace EventStore.TestClient.Commands; - -internal class TransactionWriteProcessor : ICmdProcessor -{ - public string Usage - { - get { return "TWR [ [ []]]"; } - } - - public string Keyword - { - get { return "TWR"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - var eventStreamId = "test-stream"; - var expectedVersion = ExpectedVersion.Any; - int eventsCnt = 10; - - if (args.Length > 0) - { - if (args.Length > 3) - { - return false; - } - - eventStreamId = args[0]; - if (args.Length > 1) - { - expectedVersion = args[1].ToUpper() == "ANY" ? ExpectedVersion.Any : int.Parse(args[1]); - } - - if (args.Length > 2) - { - eventsCnt = MetricPrefixValue.ParseInt(args[1]); - } - } - - context.IsAsync(); - - var sw = new Stopwatch(); - var stage = Stage.AcquiringTransactionId; - long transactionId = -1; - var writtenEvents = 0; - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information("[{remoteEndPoint}, L{localEndPoint}]: Starting transaction...", - conn.RemoteEndPoint, conn.LocalEndPoint); - sw.Start(); - - var tranStart = new TransactionStart(eventStreamId, expectedVersion, false); - var package = new TcpPackage(TcpCommand.TransactionStart, Guid.NewGuid(), tranStart.Serialize()); - conn.EnqueueSend(package.AsByteArray()); - }, - handlePackage: (conn, pkg) => - { - switch (stage) - { - case Stage.AcquiringTransactionId: - { - if (pkg.Command != TcpCommand.TransactionStartCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result != OperationResult.Success) - { - var msg = string.Format("Error while starting transaction: {0} ({1}).", dto.Message, - dto.Result); - context.Log.Information("Error while starting transaction: {message} ({e}).", dto.Message, - dto.Result); - context.Fail(reason: msg); - } - else - { - context.Log.Information("Successfully started transaction. TransactionId: {transactionId}.", - dto.TransactionId); - context.Log.Information("Now sending transactional events. TransactionId: {transactionId}", - dto.TransactionId); - - transactionId = dto.TransactionId; - stage = Stage.Writing; - for (int i = 0; i < eventsCnt; ++i) - { - var writeDto = new TransactionWrite( - transactionId, - new[] { - new NewEvent(Guid.NewGuid().ToByteArray(), - "TakeSomeSpaceEvent", - 0, 0, - Common.Utils.Helper.UTF8NoBom.GetBytes(Guid.NewGuid().ToString()), - Common.Utils.Helper.UTF8NoBom.GetBytes(Guid.NewGuid().ToString())) - }, - false); - var package = new TcpPackage(TcpCommand.TransactionWrite, Guid.NewGuid(), - writeDto.Serialize()); - conn.EnqueueSend(package.AsByteArray()); - } - } - - break; - } - case Stage.Writing: - { - if (pkg.Command != TcpCommand.TransactionWriteCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result != OperationResult.Success) - { - context.Log.Information("Error while writing transactional event: {message} ({e}).", - dto.Message, dto.Result); - var msg = String.Format("Error while writing transactional event: {0} ({1}).", - dto.Message, dto.Result); - context.Fail(reason: msg); - } - else - { - writtenEvents += 1; - if (writtenEvents == eventsCnt) - { - context.Log.Information("Written all events. Committing..."); - - stage = Stage.Committing; - var commitDto = new TransactionCommit(transactionId, false); - var package = new TcpPackage(TcpCommand.TransactionCommit, Guid.NewGuid(), - commitDto.Serialize()); - conn.EnqueueSend(package.AsByteArray()); - } - } - - break; - } - case Stage.Committing: - { - if (pkg.Command != TcpCommand.TransactionCommitCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - sw.Stop(); - - var dto = pkg.Data.Deserialize(); - if (dto.Result != OperationResult.Success) - { - var msg = string.Format("Error while committing transaction: {0} ({1}).", dto.Message, - dto.Result); - context.Log.Information("Error while committing transaction: {message} ({e}).", dto.Message, - dto.Result); - context.Log.Information("Transaction took: {elapsed}.", sw.Elapsed); - context.Fail(reason: msg); - } - else - { - context.Log.Information("Successfully committed transaction [{transactionId}]!", - dto.TransactionId); - context.Log.Information("Transaction took: {elapsed}.", sw.Elapsed); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds)); - context.Success(); - } - - conn.Close(); - break; - } - default: - throw new ArgumentOutOfRangeException(); - } - }, - connectionClosed: (connection, error) => context.Fail(reason: "Connection was closed prematurely.")); - context.WaitForCompletion(); - return true; - } - - private enum Stage - { - AcquiringTransactionId, - Writing, - Committing - } -} diff --git a/src/EventStore.TestClient/Commands/WriteFloodClientApiProcessor.cs b/src/EventStore.TestClient/Commands/WriteFloodClientApiProcessor.cs deleted file mode 100644 index c86c7ee4ab..0000000000 --- a/src/EventStore.TestClient/Commands/WriteFloodClientApiProcessor.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using EventStore.ClientAPI; -using EventStore.Common.Utils; -using ExpectedVersion = EventStore.Core.Data.ExpectedVersion; - -namespace EventStore.TestClient.Commands; - -internal class WriteFloodClientApiProcessor : ICmdProcessor -{ - public string Usage - { - get { return "WRFLCA [ [ []]]"; } - } - - public string Keyword - { - get { return "WRFLCA"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 5000; - int streamsCnt = 1000; - int size = 256; - if (args.Length > 0) - { - if (args.Length < 2 || args.Length > 4) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseLong(args[1]); - if (args.Length >= 3) - { - streamsCnt = MetricPrefixValue.ParseInt(args[2]); - } - - if (args.Length >= 4) - { - size = MetricPrefixValue.ParseInt(args[3]); - } - } - catch - { - return false; - } - } - - WriteFlood(context, clientsCnt, requestsCnt, streamsCnt, size); - return true; - } - - private void WriteFlood(CommandProcessorContext context, int clientsCnt, long requestsCnt, int streamsCnt, - int size) - { - context.IsAsync(); - - var doneEvent = new ManualResetEventSlim(false); - var clients = new List(); - var threads = new List(); - - long succ = 0; - - var streams = Enumerable.Range(0, streamsCnt).Select(x => Guid.NewGuid().ToString()).ToArray(); - var sw2 = new Stopwatch(); - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - var rnd = new Random(); - - var settings = ConnectionSettings.Create() - .UseConsoleLogger() - .PerformOnAnyNode() - .LimitReconnectionsTo(10) - .LimitRetriesForOperationTo(10) - .LimitOperationsQueueTo(10000) - .LimitConcurrentOperationsTo(context._tcpTestClient.Options.WriteWindow / clientsCnt) - .FailOnNoServerResponse(); - - var client = EventStoreConnection.Create(settings, - new Uri($"tcp://{context._tcpTestClient.TcpEndpoint.GetHost()}:{context._tcpTestClient.TcpEndpoint.GetPort()}")); - clients.Add(client); - - threads.Add(new Thread(_ => - { - client.ErrorOccurred += (s, e) => context.Fail(e.Exception, "Error on connection"); - client.ConnectAsync().Wait(); - - for (int j = 0; j < count; ++j) - { - var task = client.AppendToStreamAsync(streams[rnd.Next(streamsCnt)], - ExpectedVersion.Any, - new EventData(Guid.NewGuid(), - "TakeSomeSpaceEvent", - false, - Common.Utils.Helper.UTF8NoBom.GetBytes("DATA" + new string('*', size)), - Common.Utils.Helper.UTF8NoBom.GetBytes("METADATA" + new string('$', 100)))); - task.ContinueWith(x => - { - if (x.IsFaulted) - { - context.Fail(x.Exception.InnerException, "Error on writing operation."); - return; - } - - var localAll = Interlocked.Increment(ref succ); - if (localAll % 1000 == 0) - { - Console.Write('.'); - } - - if (localAll % 100000 == 0) - { - var elapsed = sw2.Elapsed; - sw2.Restart(); - context.Log.Debug("\nDONE TOTAL {writes} WRITES IN {elapsed} ({rate:0.0}/s).", - localAll, - elapsed, - 1000.0 * 100000 / elapsed.TotalMilliseconds); - } - - if (localAll == requestsCnt) - { - context.Success(); - doneEvent.Set(); - } - }); - } - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - sw2.Start(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - clients.ForEach(x => x.Close()); - sw.Stop(); - - context.Log.Information("Completed. Successes: {success}.", succ); - - var reqPerSec = (succ + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", succ, - sw.ElapsedMilliseconds, reqPerSec); - - var fail = requestsCnt - succ; - PerfUtils.LogData( - Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("successes", succ), PerfUtils.Col("failures", fail))); - var failuresRate = (int)(100 * fail / (fail + succ)); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-{1}-{2}-failureSuccessRate", Keyword, clientsCnt, requestsCnt), failuresRate); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-c{1}-r{2}-st{3}-s{4}-reqPerSec", Keyword, clientsCnt, requestsCnt, streamsCnt, size), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-c{1}-r{2}-st{3}-s{4}-failureSuccessRate", Keyword, clientsCnt, requestsCnt, - streamsCnt, size), failuresRate); - - if (Interlocked.Read(ref succ) != requestsCnt) - { - context.Fail(reason: "There were errors or not all requests completed."); - } - else - { - context.Success(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/WriteFloodProcessor.cs b/src/EventStore.TestClient/Commands/WriteFloodProcessor.cs deleted file mode 100644 index fdb5ea482d..0000000000 --- a/src/EventStore.TestClient/Commands/WriteFloodProcessor.cs +++ /dev/null @@ -1,264 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using EventStore.Client.Messages; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.TestClient.Statistics; -using EventStore.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class WriteFloodProcessor : ICmdProcessor -{ - public string Usage - { - // 0 1 2 3 4 5 - get { return "WRFL [ [ [ [ []]]]]"; } - } - - public string Keyword - { - get { return "WRFL"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - long requestsCnt = 5000; - int streamsCnt = 1000; - int size = 256; - int batchSize = 1; - string streamNamePrefix = string.Empty; - if (args.Length > 0) - { - if (args.Length < 2 || args.Length > 6) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseLong(args[1]); - if (args.Length >= 3) - { - streamsCnt = MetricPrefixValue.ParseInt(args[2]); - } - - if (args.Length >= 4) - { - size = MetricPrefixValue.ParseInt(args[3]); - } - - if (args.Length >= 5) - { - batchSize = MetricPrefixValue.ParseInt(args[4]); - } - - if (args.Length >= 6) - { - streamNamePrefix = args[5]; - } - } - catch - { - return false; - } - } - - var stats = new WriteFloodStats(Keyword, context.OutputCsv, args); - var monitor = new RequestMonitor(); - WriteFlood(context, stats, clientsCnt, requestsCnt, streamsCnt, size, batchSize, streamNamePrefix, monitor); - return true; - } - - private void WriteFlood(CommandProcessorContext context, WriteFloodStats stats, int clientsCnt, long requestsCnt, int streamsCnt, - int size, int batchSize, string streamNamePrefix, RequestMonitor monitor) - { - context.IsAsync(); - - var doneEvent = new ManualResetEventSlim(false); - var clients = new List>(); - var threads = new List(); - - long last = 0; - - string[] streams = Enumerable.Range(0, streamsCnt).Select(x => - string.IsNullOrWhiteSpace(streamNamePrefix) - ? Guid.NewGuid().ToString() - : $"{streamNamePrefix}-{x}" - ).ToArray(); - - context.Log.Information("Writing streams randomly between {first} and {last}", - streams.FirstOrDefault(), - streams.LastOrDefault()); - - Console.WriteLine($"Last stream: {streams.LastOrDefault()}"); - stats.StartTime = DateTime.UtcNow; - var sw2 = new Stopwatch(); - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - long sent = 0; - long received = 0; - var rnd = new Random(); - var client = context._tcpTestClient.CreateTcpConnection( - context, - (conn, pkg) => - { - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - monitor.EndOperation(pkg.CorrelationId); - switch (dto.Result) - { - case OperationResult.Success: - Interlocked.Add(ref stats.Succ, batchSize); - if (stats.Succ - last > 1000) - { - last = stats.Succ; - Console.Write("."); - } - - break; - case OperationResult.PrepareTimeout: - Interlocked.Increment(ref stats.PrepTimeout); - break; - case OperationResult.CommitTimeout: - Interlocked.Increment(ref stats.CommitTimeout); - break; - case OperationResult.ForwardTimeout: - Interlocked.Increment(ref stats.ForwardTimeout); - break; - case OperationResult.WrongExpectedVersion: - Interlocked.Increment(ref stats.WrongExpVersion); - break; - case OperationResult.StreamDeleted: - Interlocked.Increment(ref stats.StreamDeleted); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - if (dto.Result != OperationResult.Success) - { - if (Interlocked.Increment(ref stats.Fail) % 1000 == 0) - { - Console.Write('#'); - } - } - - Interlocked.Increment(ref received); - var localAll = Interlocked.Add(ref stats.All, batchSize); - if (localAll % 100000 == 0) - { - stats.Elapsed = sw2.Elapsed; - stats.Rate = 1000.0 * 100000 / stats.Elapsed.TotalMilliseconds; - sw2.Restart(); - context.Log.Debug( - "\nDONE TOTAL {writes} WRITES IN {elapsed} ({rate:0.0}/s) [S:{success}, F:{failures} (WEV:{wrongExpectedVersion}, " + - "P:{prepareTimeout}, C:{commitTimeout}, F:{forwardTimeout}, D:{streamDeleted})].", - localAll, stats.Elapsed, stats.Rate, stats.Succ, stats.Fail, - stats.WrongExpVersion, stats.PrepTimeout, stats.CommitTimeout, stats.ForwardTimeout, stats.StreamDeleted); - stats.WriteStatsToFile(context.StatsLogger); - } - - if (localAll >= requestsCnt) - { - context.Success(); - doneEvent.Set(); - } - }, - connectionClosed: (conn, err) => context.Fail(reason: "Connection was closed prematurely.")); - clients.Add(client); - - threads.Add(new Thread(() => - { - for (int j = 0; j < count; ++j) - { - var events = new NewEvent[batchSize]; - for (int q = 0; q < batchSize; q++) - { - events[q] = new NewEvent(Guid.NewGuid().ToByteArray(), - "TakeSomeSpaceEvent", - 1, 0, - Common.Utils.Helper.UTF8NoBom.GetBytes( - "{ \"DATA\" : \"" + new string('*', size) + "\"}"), - Common.Utils.Helper.UTF8NoBom.GetBytes( - "{ \"METADATA\" : \"" + new string('$', 100) + "\"}")); - } - - var corrid = Guid.NewGuid(); - var write = new WriteEvents( - streams[rnd.Next(streamsCnt)], - ExpectedVersion.Any, - events, - false); - var package = new TcpPackage(TcpCommand.WriteEvents, corrid, write.Serialize()); - monitor.StartOperation(corrid); - client.EnqueueSend(package.AsByteArray()); - - var localSent = Interlocked.Increment(ref sent); - while (localSent - Interlocked.Read(ref received) > - context._tcpTestClient.Options.WriteWindow / clientsCnt) - { - Thread.Sleep(1); - } - } - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - sw2.Start(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - sw.Stop(); - clients.ForEach(client => client.Close()); - - stats.WriteStatsToFile(context.StatsLogger); - context.Log.Information( - "Completed. Successes: {success}, failures: {failures} (WRONG VERSION: {wrongExpectedVersion}, P: {prepareTimeout}, " + - "C: {commitTimeout}, F: {forwardTimeout}, D: {streamDeleted})", - stats.Succ, stats.Fail, stats.WrongExpVersion, stats.PrepTimeout, stats.CommitTimeout, stats.ForwardTimeout, stats.StreamDeleted); - - var reqPerSec = (stats.All + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", stats.All, - sw.ElapsedMilliseconds, reqPerSec); - - PerfUtils.LogData( - Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("successes", stats.Succ), PerfUtils.Col("failures", stats.Fail))); - - var failuresRate = (int)(100 * stats.Fail / (stats.Fail + stats.Succ)); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-{1}-{2}-failureSuccessRate", Keyword, clientsCnt, requestsCnt), failuresRate); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-c{1}-r{2}-st{3}-s{4}-reqPerSec", Keyword, clientsCnt, requestsCnt, streamsCnt, size), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-c{1}-r{2}-st{3}-s{4}-failureSuccessRate", Keyword, clientsCnt, requestsCnt, - streamsCnt, size), failuresRate); - monitor.GetMeasurementDetails(); - if (Interlocked.Read(ref stats.Succ) != requestsCnt) - { - context.Fail(reason: "There were errors or not all requests completed."); - } - else - { - context.Success(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/WriteFloodWaitingProcessor.cs b/src/EventStore.TestClient/Commands/WriteFloodWaitingProcessor.cs deleted file mode 100644 index 88e9859429..0000000000 --- a/src/EventStore.TestClient/Commands/WriteFloodWaitingProcessor.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using EventStore.Client.Messages; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class WriteFloodWaitingProcessor : ICmdProcessor -{ - public string Usage - { - get { return "WRFLW [ [payload-size]]"; } - } - - public string Keyword - { - get { return "WRFLW"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - int requestsCnt = 5000; - int payloadSize = 256 + 100; - if (args.Length > 0) - { - if (args.Length > 3 || args.Length < 2) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - requestsCnt = MetricPrefixValue.ParseInt(args[1]); - if (args.Length == 3) - { - payloadSize = MetricPrefixValue.ParseInt(args[2]); - } - } - catch - { - return false; - } - } - - WriteFlood(context, clientsCnt, requestsCnt, payloadSize); - return true; - } - - private void WriteFlood(CommandProcessorContext context, int clientsCnt, int requestsCnt, int payloadSize) - { - context.IsAsync(); - - var dataSize = Math.Max(0, payloadSize - 100); - var metadataSize = Math.Min(100, payloadSize); - - var clients = new List>(); - var threads = new List(); - var doneEvent = new ManualResetEventSlim(false); - var succ = 0; - var fail = 0; - var all = 0; - for (int i = 0; i < clientsCnt; i++) - { - var count = requestsCnt / clientsCnt + ((i == clientsCnt - 1) ? requestsCnt % clientsCnt : 0); - var autoEvent = new AutoResetEvent(false); - var eventStreamId = "es" + Guid.NewGuid(); - var received = 0; - var client = context._tcpTestClient.CreateTcpConnection( - context, - (conn, pkg) => - { - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - Interlocked.Increment(ref received); - var dto = pkg.Data.Deserialize(); - if (dto.Result == OperationResult.Success) - { - if (Interlocked.Increment(ref succ) % 1000 == 0) - { - Console.Write("."); - } - } - else - { - if (Interlocked.Increment(ref fail) % 1000 == 0) - { - Console.Write("#"); - } - } - - if (Interlocked.Increment(ref all) == requestsCnt) - { - doneEvent.Set(); - } - - autoEvent.Set(); - }, - connectionClosed: (conn, err) => context.Fail(reason: "Connection was closed prematurely.")); - clients.Add(client); - - threads.Add(new Thread(() => - { - for (int j = 0; j < count; ++j) - { - var write = new WriteEvents( - eventStreamId, - ExpectedVersion.Any, - new[] { - new NewEvent(Guid.NewGuid().ToByteArray(), - "TakeSomeSpaceEvent", - 0, 0, - Common.Utils.Helper.UTF8NoBom.GetBytes("DATA" + new string('*', dataSize)), - Common.Utils.Helper.UTF8NoBom.GetBytes("METADATA" + new string('$', metadataSize))) - }, - false); - var package = new TcpPackage(TcpCommand.WriteEvents, Guid.NewGuid(), write.Serialize()); - client.EnqueueSend(package.AsByteArray()); - autoEvent.WaitOne(); - } - }) - { IsBackground = true }); - } - - var sw = Stopwatch.StartNew(); - threads.ForEach(thread => thread.Start()); - doneEvent.Wait(); - sw.Stop(); - clients.ForEach(client => client.Close()); - - var reqPerSec = (all + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("Completed. Successes: {success}, failures: {failures}", succ, fail); - context.Log.Information( - "{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec, latency: {latency:0.00} ms).", - all, sw.ElapsedMilliseconds, reqPerSec, (sw.Elapsed.TotalMilliseconds + 0.0) / requestsCnt); - PerfUtils.LogData(Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("successes", succ), PerfUtils.Col("failures", fail))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-{1}-{2}-failureSuccessRate", Keyword, clientsCnt, requestsCnt), - (int)(100.0 * fail / (fail + succ))); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds / requestsCnt)); - - if (succ != requestsCnt) - { - context.Fail(reason: "There were errors or not all requests completed."); - } - else - { - context.Success(); - } - } -} diff --git a/src/EventStore.TestClient/Commands/WriteJsonProcessor.cs b/src/EventStore.TestClient/Commands/WriteJsonProcessor.cs deleted file mode 100644 index 750cf8a8c0..0000000000 --- a/src/EventStore.TestClient/Commands/WriteJsonProcessor.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net.Sockets; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; - - -namespace EventStore.TestClient.Commands; - -internal class WriteJsonProcessor : ICmdProcessor -{ - public string Usage - { - get { return "WRJ [ []]"; } - } - - public string Keyword - { - get { return "WRJ"; } - } - - private readonly Random _random = new Random(); - - public bool Execute(CommandProcessorContext context, string[] args) - { - var eventStreamId = "test-stream"; - var expectedVersion = ExpectedVersion.Any; - var data = GenerateTestData(); - string metadata = null; - - if (args.Length > 0) - { - if (args.Length < 3 || args.Length > 4) - { - return false; - } - - eventStreamId = args[0]; - expectedVersion = args[1].ToUpper() == "ANY" ? ExpectedVersion.Any : int.Parse(args[1]); - data = args[2]; - if (args.Length == 4) - { - metadata = args[3]; - } - } - - context.IsAsync(); - var writeDto = new WriteEvents( - eventStreamId, - expectedVersion, - new[] { - new NewEvent(Guid.NewGuid().ToByteArray(), - "JsonDataEvent", - 1, 0, - Helper.UTF8NoBom.GetBytes(data), - Helper.UTF8NoBom.GetBytes(metadata ?? string.Empty)) - }, - false); - var package = new TcpPackage(TcpCommand.WriteEvents, Guid.NewGuid(), writeDto.Serialize()); - - var sw = new Stopwatch(); - bool dataReceived = false; - - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information("[{remoteEndPoint}, L{localEndPoint}]: Writing...", conn.RemoteEndPoint, - conn.LocalEndPoint); - sw.Start(); - conn.EnqueueSend(package.AsByteArray()); - }, - handlePackage: (conn, pkg) => - { - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - dataReceived = true; - sw.Stop(); - - var dto = pkg.Data.Deserialize(); - if (dto.Result == OperationResult.Success) - { - context.Log.Information("Successfully written. EventId: {correlationId}.", package.CorrelationId); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds)); - } - else - { - context.Log.Information("Error while writing: {message} ({e}).", dto.Message, dto.Result); - } - - context.Log.Information("Write request took: {elapsed}.", sw.Elapsed); - conn.Close(); - context.Success(); - }, - connectionClosed: (connection, error) => - { - if (dataReceived && error == SocketError.Success) - { - context.Success(); - } - else - { - context.Fail(); - } - }); - - context.WaitForCompletion(); - return true; - } - - private string GenerateTestData() - { - return TestClientJson.To(new TestData(Guid.NewGuid().ToString(), _random.Next(1, 101))); - } -} - -internal class TestData -{ - public string Name { get; set; } - public int Version { get; set; } - - public TestData() - { - } - - public TestData(string name, int version) - { - Name = name; - Version = version; - } -} diff --git a/src/EventStore.TestClient/Commands/WriteLongTermProcessor.cs b/src/EventStore.TestClient/Commands/WriteLongTermProcessor.cs deleted file mode 100644 index 559f865ab5..0000000000 --- a/src/EventStore.TestClient/Commands/WriteLongTermProcessor.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class WriteLongTermProcessor : ICmdProcessor -{ - public string Usage - { - get - { - return - "WRLT []"; - } - } - - public string Keyword - { - get { return "WRLT"; } - } - - private readonly object _randomLockRoot = new object(); - private readonly Random _random = new Random(); - - public bool Execute(CommandProcessorContext context, string[] args) - { - int clientsCnt = 1; - int minPerSecond = 1; - int maxPerSecond = 2; - int runTimeMinutes = 1; - string eventStreamId = null; - - if (args.Length > 0) - { - if (args.Length != 4 && args.Length != 5) - { - return false; - } - - try - { - clientsCnt = MetricPrefixValue.ParseInt(args[0]); - minPerSecond = MetricPrefixValue.ParseInt(args[1]); - maxPerSecond = MetricPrefixValue.ParseInt(args[2]); - runTimeMinutes = MetricPrefixValue.ParseInt(args[3]); - if (args.Length == 5) - { - eventStreamId = args[4]; - } - } - catch - { - return false; - } - } - - Flood(context, eventStreamId, clientsCnt, minPerSecond, maxPerSecond, runTimeMinutes); - return true; - } - - private void Flood(CommandProcessorContext context, - string eventStreamId, - int clientsCnt, - int minPerSecond, - int maxPerSecond, - int runTimeMinutes) - { - context.IsAsync(); - - var clients = new List>(); - var threads = new List(); - var doneEvent = new ManualResetEvent(false); - var done = false; - - var succ = 0; - var fail = 0; - - var requestsCnt = 0; - - int sent = 0; - int received = 0; - - var watchLockRoot = new object(); - var sw = Stopwatch.StartNew(); - for (int i = 0; i < clientsCnt; i++) - { - var esId = eventStreamId ?? "Stream-" + Thread.CurrentThread.ManagedThreadId % 3; - - var client = context._tcpTestClient.CreateTcpConnection( - context, - (conn, pkg) => - { - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result == OperationResult.Success) - { - var succDone = Interlocked.Increment(ref succ); - if (succDone % maxPerSecond == 0) - { - Console.Write("."); - } - - Interlocked.Increment(ref requestsCnt); - } - else - { - Interlocked.Increment(ref fail); - } - - Interlocked.Increment(ref received); - }, - connectionClosed: (conn, err) => - { - if (!done) - { - context.Fail(reason: "Socket was closed, but not all requests were completed."); - } - else - { - context.Success(); - } - }); - clients.Add(client); - - threads.Add(new Thread(() => - { - var sentCount = 0; - var sleepTime = 0; - - var dataSizeCoefficient = 1; - var currentMinute = -1; - - while (true) - { - TimeSpan elapsed; - lock (watchLockRoot) - { - elapsed = sw.Elapsed; - } - - if (elapsed.TotalMinutes > runTimeMinutes) - { - done = true; - doneEvent.Set(); - break; - } - - if (sentCount == 0) - { - int elapsedMinutesInt = (int)elapsed.TotalMinutes; - lock (_randomLockRoot) - { - sentCount = minPerSecond == maxPerSecond - ? maxPerSecond - : _random.Next(minPerSecond, maxPerSecond); - dataSizeCoefficient = _random.Next(8, 256); - } - - if (currentMinute != elapsedMinutesInt) - { - currentMinute = elapsedMinutesInt; - context.Log.Information( - "\nElapsed {elapsed} of {runTime} minutes, sent {sent}; next block coef. {dataSizeCoefficient}", - elapsedMinutesInt, - runTimeMinutes, - sent, - dataSizeCoefficient); - } - - sleepTime = 1000 / sentCount; - } - - var dataSize = dataSizeCoefficient * 8; - var write = new WriteEvents( - esId, - ExpectedVersion.Any, - new[] { - new NewEvent( - Guid.NewGuid().ToByteArray(), - "TakeSomeSpaceEvent", - 0, 0, - Helper.UTF8NoBom.GetBytes( - "DATA" + dataSize.ToString(" 00000 ") + new string('*', dataSize)), - Helper.UTF8NoBom.GetBytes("METADATA" + new string('$', 100))) - }, - false); - var package = new TcpPackage(TcpCommand.WriteEvents, Guid.NewGuid(), write.Serialize()); - client.EnqueueSend(package.AsByteArray()); - - Interlocked.Increment(ref sent); - - Thread.Sleep(sleepTime); - sentCount -= 1; - - while (sent - received > context._tcpTestClient.Options.WriteWindow / clientsCnt) - { - Thread.Sleep(1); - } - } - })); - } - - foreach (var thread in threads) - { - thread.IsBackground = true; - thread.Start(); - } - - doneEvent.WaitOne(); - sw.Stop(); - - foreach (var client in clients) - { - client.Close(); - } - - context.Log.Information("Completed. Successes: {success}, failures: {failures}", succ, fail); - var reqPerSec = (requestsCnt + 0.0) / sw.ElapsedMilliseconds * 1000; - context.Log.Information("{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", - requestsCnt, - sw.ElapsedMilliseconds, - reqPerSec); - - PerfUtils.LogData( - Keyword, - PerfUtils.Row(PerfUtils.Col("clientsCnt", clientsCnt), - PerfUtils.Col("requestsCnt", requestsCnt), - PerfUtils.Col("ElapsedMilliseconds", sw.ElapsedMilliseconds)), - PerfUtils.Row(PerfUtils.Col("successes", succ), PerfUtils.Col("failures", fail)) - ); - - PerfUtils.LogTeamCityGraphData(string.Format("{0}-{1}-{2}-reqPerSec", Keyword, clientsCnt, requestsCnt), - (int)reqPerSec); - - PerfUtils.LogTeamCityGraphData( - string.Format("{0}-{1}-{2}-failureSuccessRate", Keyword, clientsCnt, requestsCnt), - 100 * fail / (fail + succ)); - - context.Success(); - } -} diff --git a/src/EventStore.TestClient/Commands/WriteProcessor.cs b/src/EventStore.TestClient/Commands/WriteProcessor.cs deleted file mode 100644 index 213b3a5b2b..0000000000 --- a/src/EventStore.TestClient/Commands/WriteProcessor.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Diagnostics; -using EventStore.Client.Messages; -using EventStore.Common.Utils; -using EventStore.Core.Data; -using EventStore.Core.Services.Transport.Tcp; - -namespace EventStore.TestClient.Commands; - -internal class WriteProcessor : ICmdProcessor -{ - public string Usage - { - get { return "WR [ [ [ [ ]]]"; } - } - - public string Keyword - { - get { return "WR"; } - } - - public bool Execute(CommandProcessorContext context, string[] args) - { - var eventStreamId = "test-stream"; - var expectedVersion = ExpectedVersion.Any; - var data = "test-data"; - string metadata = null; - bool isJson = false; - string login = null; - string pass = null; - - if (args.Length > 0) - { - if (args.Length < 3 || args.Length > 7 || args.Length == 6) - { - return false; - } - - eventStreamId = args[0]; - expectedVersion = args[1].ToUpper() == "ANY" ? ExpectedVersion.Any : int.Parse(args[1]); - data = args[2]; - if (args.Length >= 4) - { - metadata = args[3]; - } - - if (args.Length >= 5) - { - isJson = bool.Parse(args[4]); - } - - if (args.Length >= 7) - { - login = args[5]; - pass = args[6]; - } - } - - context.IsAsync(); - var sw = new Stopwatch(); - context._tcpTestClient.CreateTcpConnection( - context, - connectionEstablished: conn => - { - context.Log.Information("[{remoteEndPoint}, L{localEndPoint}]: Writing...", conn.RemoteEndPoint, - conn.LocalEndPoint); - var writeDto = new WriteEvents( - eventStreamId, - expectedVersion, - new[] { - new NewEvent(Guid.NewGuid().ToByteArray(), - "TakeSomeSpaceEvent", - isJson ? 1 : 0, 0, - Helper.UTF8NoBom.GetBytes(data), - Helper.UTF8NoBom.GetBytes(metadata ?? string.Empty)) - }, - false); - var package = new TcpPackage(TcpCommand.WriteEvents, - login == null ? TcpFlags.None : TcpFlags.Authenticated, - Guid.NewGuid(), - login, - pass, - writeDto.Serialize()).AsByteArray(); - sw.Start(); - conn.EnqueueSend(package); - }, - handlePackage: (conn, pkg) => - { - sw.Stop(); - context.Log.Information("Write request took: {elapsed}.", sw.Elapsed); - - if (pkg.Command != TcpCommand.WriteEventsCompleted) - { - context.Fail(reason: string.Format("Unexpected TCP package: {0}.", pkg.Command)); - return; - } - - var dto = pkg.Data.Deserialize(); - if (dto.Result == EventStore.Client.Messages.OperationResult.Success) - { - context.Log.Information("Successfully written."); - PerfUtils.LogTeamCityGraphData(string.Format("{0}-latency-ms", Keyword), - (int)Math.Round(sw.Elapsed.TotalMilliseconds)); - context.Success(); - } - else - { - context.Log.Information("Error while writing: {message} ({e}).", dto.Message, dto.Result); - context.Fail(); - } - - conn.Close(); - }, - connectionClosed: (connection, error) => context.Fail(reason: "Connection was closed prematurely.")); - - context.WaitForCompletion(); - return true; - } -} diff --git a/src/EventStore.TestClient/ConsoleTable.cs b/src/EventStore.TestClient/ConsoleTable.cs deleted file mode 100644 index 3bcc986e46..0000000000 --- a/src/EventStore.TestClient/ConsoleTable.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using EventStore.Common.Utils; -#pragma warning disable 1591 - -namespace EventStore.TestClient; - -public class ConsoleTable -{ - private readonly string[] _header; - private readonly int[] _columnWidths; - private readonly List _rows; - - public ConsoleTable(params string[] header) - { - Ensure.NotNull(header, "header"); - - _header = header; - _columnWidths = _header.Select(s => s.Length + 4).ToArray(); - _rows = new List(); - - AppendRow(_header); - } - - public string CreateIndentedTable() - { - return string.Format("{0}{1}{0}", Environment.NewLine, CreateTable()); - } - - public string CreateTable() - { - var lineSeparator = RowSeparator(_columnWidths.Sum() + _columnWidths.Length + 1); - - var table = new StringBuilder(); - foreach (var row in _rows) - { - table.AppendLine(lineSeparator); - table.AppendLine(row); - } - - table.Append(lineSeparator); - - return table.ToString(); - } - - private string RowSeparator(int width) - { - return new string('-', width); - } - - public void AppendRow(params string[] cells) - { - if (cells == null || cells.Length != _header.Length) - { - return; - } - - var row = new StringBuilder(); - for (int i = 0; i < cells.Length; i++) - { - var format = "|{0," + _columnWidths[i] + "}"; - row.AppendFormat(format, cells[i]); - } - - row.Append("|"); - - _rows.Add(row.ToString()); - } -} diff --git a/src/EventStore.TestClient/EventStore.TestClient.csproj b/src/EventStore.TestClient/EventStore.TestClient.csproj index 57f7843616..b9ab45a52e 100644 --- a/src/EventStore.TestClient/EventStore.TestClient.csproj +++ b/src/EventStore.TestClient/EventStore.TestClient.csproj @@ -5,8 +5,8 @@ false - + @@ -18,15 +18,17 @@ - - logconfig.json PreserveNewest + + version.properties + PreserveNewest + diff --git a/src/EventStore.TestClient/GrpcCommands/CompatibilityProcessors.cs b/src/EventStore.TestClient/GrpcCommands/CompatibilityProcessors.cs new file mode 100644 index 0000000000..cb2fb3aa7b --- /dev/null +++ b/src/EventStore.TestClient/GrpcCommands/CompatibilityProcessors.cs @@ -0,0 +1,916 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using EventStore.Client; +using EventStore.TestClient.Commands; + +namespace EventStore.TestClient.GrpcCommands; + +internal sealed class CompatibilityProcessor : ICmdProcessor +{ + private static readonly UTF8Encoding Utf8NoBom = new(false); + private readonly Func _execute; + private readonly Func _validate; + + public CompatibilityProcessor( + string keyword, + string usage, + Func execute, + Func validate = null) + { + Keyword = keyword; + Usage = usage; + _execute = execute; + _validate = validate ?? (_ => true); + } + + public string Keyword { get; } + public string Usage { get; } + + public bool Execute(CommandProcessorContext context, string[] args) + { + if (!_validate(args)) + { + return false; + } + + context.IsAsync(); + _ = Complete(context, args); + return true; + } + + private async Task Complete(CommandProcessorContext context, string[] args) + { + try + { + await _execute(context, args); + context.Success(); + } + catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested) + { + context.Success(); + } + catch (Exception ex) + { + context.Fail(ex); + } + } + + public static IEnumerable CreateSupportedProcessors() + { + yield return new CompatibilityProcessor("PING", "PING", Ping, args => args.Length == 0); + yield return new CompatibilityProcessor("PINGFL", "PINGFL [ ]", PingFlood, + args => args.Length == 0 || args.Length == 2); + yield return new CompatibilityProcessor("PINGFLW", "PINGFLW [ ]", PingFloodWaiting, + args => args.Length == 0 || args.Length == 2); + yield return new CompatibilityProcessor("WR", "WR [ [ [ [ ]]]", Write, + args => args.Length == 0 || args.Length is >= 3 and <= 7 && args.Length != 6); + yield return new CompatibilityProcessor("WRJ", "WRJ [ []]", WriteJson, + args => args.Length == 0 || args.Length is >= 3 and <= 4); + yield return new CompatibilityProcessor("MWR", "MWR [ [ []]]", MultiWrite, + args => args.Length <= 3); + yield return new CompatibilityProcessor("WRFLW", "WRFLW [ [payload-size]]", WriteFloodWaiting, + args => args.Length == 0 || args.Length is 2 or 3); + yield return new CompatibilityProcessor("MWRFLW", "MWRFLW [ [ ]]", MultiWriteFloodWaiting, + args => args.Length is 0 or 1 or 3); + yield return new CompatibilityProcessor("DEL", "DEL [ []]", Delete, + args => args.Length <= 2); + yield return new CompatibilityProcessor("RD", "RD [ [ []]]", Read, + args => args.Length <= 3); + yield return new CompatibilityProcessor("RDFL", "RDFL [ [ [ []]]]", ReadFlood, + args => args.Length == 0 || args.Length is >= 2 and <= 5); + yield return new CompatibilityProcessor("RDALL", "RDALL [[F|B] [ []]]", ReadAll, + args => args.Length is 0 or 1 or 3 or 4 && + (args.Length == 0 || args[0].Equals("F", StringComparison.OrdinalIgnoreCase) || + args[0].Equals("B", StringComparison.OrdinalIgnoreCase))); + yield return new CompatibilityProcessor("WRLT", "WRLT []", WriteLongTerm, + args => args.Length == 0 || args.Length is 4 or 5); + yield return new CompatibilityProcessor("SUBSCR", "SUBSCR [ ... ]", Subscribe, + args => true); + yield return new CompatibilityProcessor("SST", "SST []", SubscriptionStress, + args => args.Length <= 1); + yield return new CompatibilityProcessor("SCAVENGE", "SCAVENGE", Scavenge, + args => args.Length == 0); + yield return new CompatibilityProcessor("VERIFY", "VERIFY ", Verify, + args => args.Length == 0 || args.Length == 5); + yield return new CompatibilityProcessor("CHKGRPC", "CHKGRPC", CheckGrpcSanitization, + args => args.Length == 0); + } + + private static EventData Event( + string data = "test-data", + string metadata = "", + bool isJson = false, + string eventType = "TakeSomeSpaceEvent") => + new(Uuid.FromGuid(Guid.NewGuid()), eventType, Utf8NoBom.GetBytes(data), Utf8NoBom.GetBytes(metadata), + isJson ? "application/json" : "application/octet-stream"); + + private static async Task Append( + EventStoreClient client, + string stream, + ExpectedRevision expected, + IEnumerable events, + UserCredentials credentials, + CancellationToken cancellationToken) + { + if (expected.Revision is { } revision) + { + await client.AppendToStreamAsync(stream, revision, events, userCredentials: credentials, cancellationToken: cancellationToken); + } + else + { + await client.AppendToStreamAsync(stream, expected.State, events, userCredentials: credentials, cancellationToken: cancellationToken); + } + } + + private static async Task Ping(CommandProcessorContext context, string[] args) + { + var client = context._grpcTestClient.CreateGrpcClient(requireLeader: false); + await Ping(client, context.CancellationToken); + } + + private static async Task Ping(EventStoreClient client, CancellationToken cancellationToken) + { + var read = client.ReadStreamAsync(Direction.Forwards, $"test-client-ping-{Guid.NewGuid():N}", StreamPosition.Start, + maxCount: 1, cancellationToken: cancellationToken); + await read.ReadState; + } + + private static async Task PingFlood(CommandProcessorContext context, string[] args) + { + var workload = FloodWorkload.Parse( + args, + defaultRequestCount: 1_000_000, + maxInFlight: context._grpcTestClient.Options.PingWindow); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + await Task.WhenAll(Enumerable.Range(0, workload.ClientCount).Select(clientIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(requireLeader: false); + return RunBounded( + workload.RequestsForClient(clientIndex), + workload.MaxInFlightPerClient, + () => Ping(client, context.CancellationToken)); + })); + stopwatch.Stop(); + LogFloodCompletion(context, "PINGFL", workload, stopwatch.Elapsed); + } + + private static async Task PingFloodWaiting(CommandProcessorContext context, string[] args) + { + var workload = FloodWorkload.Parse(args, defaultRequestCount: 100_000, maxInFlight: 1); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + await Task.WhenAll(Enumerable.Range(0, workload.ClientCount).Select(async clientIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(requireLeader: false); + for (long i = 0; i < workload.RequestsForClient(clientIndex); i++) + { + await Ping(client, context.CancellationToken); + } + })); + stopwatch.Stop(); + LogFloodCompletion(context, "PINGFLW", workload, stopwatch.Elapsed, includeLatency: true); + } + + private static void LogFloodCompletion( + CommandProcessorContext context, + string command, + FloodWorkload workload, + TimeSpan elapsed, + bool includeLatency = false) + { + var rate = elapsed.TotalMilliseconds == 0 + ? 0 + : workload.RequestCount / elapsed.TotalMilliseconds * 1_000; + context.Log.Information( + "{requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", + workload.RequestCount, + elapsed.TotalMilliseconds, + rate); + PerfUtils.LogData( + command, + PerfUtils.Row( + PerfUtils.Col("clientsCnt", workload.ClientCount), + PerfUtils.Col("requestsCnt", workload.RequestCount), + PerfUtils.Col("ElapsedMilliseconds", elapsed.TotalMilliseconds))); + PerfUtils.LogTeamCityGraphData( + $"{command}-{workload.ClientCount}-{workload.RequestCount}-reqPerSec", + (int)rate); + if (includeLatency && workload.RequestCount > 0) + { + PerfUtils.LogTeamCityGraphData( + $"{command}-latency-ms", + (int)Math.Round(elapsed.TotalMilliseconds / workload.RequestCount)); + } + } + + private static async Task RunBounded(long requestCount, int maxInFlight, Func operation) + { + var pending = new List(maxInFlight); + for (long request = 0; request < requestCount; request++) + { + pending.Add(operation()); + if (pending.Count < maxInFlight) + { + continue; + } + + await Task.WhenAny(pending); + for (var index = pending.Count - 1; index >= 0; index--) + { + if (pending[index].IsCompleted) + { + await pending[index]; + pending.RemoveAt(index); + } + } + } + + await Task.WhenAll(pending); + } + + private static async Task Write(CommandProcessorContext context, string[] args) + { + var stream = args.Length == 0 ? "test-stream" : args[0]; + var expected = ExpectedRevision.Parse(args.Length == 0 ? "ANY" : args[1]); + var data = args.Length == 0 ? "test-data" : args[2]; + var metadata = args.Length >= 4 ? args[3] : ""; + var isJson = args.Length >= 5 && bool.Parse(args[4]); + var credentials = args.Length == 7 ? new UserCredentials(args[5], args[6]) : null; + await Append(context._grpcTestClient.CreateGrpcClient(), stream, expected, [Event(data, metadata, isJson)], + credentials, context.CancellationToken); + } + + private static Task WriteJson(CommandProcessorContext context, string[] args) + { + if (args.Length == 0) + { + return Write(context, ["test-stream", "ANY", "{\"value\":\"test\"}", "", "true"]); + } + + var forwarded = args.Concat(["true"]).ToArray(); + if (args.Length == 3) + { + forwarded = [args[0], args[1], args[2], "", "true"]; + } + + return Write(context, forwarded); + } + + private static async Task MultiWrite(CommandProcessorContext context, string[] args) + { + var count = args.Length == 0 ? 10 : MetricPrefixValue.ParseInt(args[0]); + var stream = args.Length >= 2 ? args[1] : "test-stream"; + var expected = ExpectedRevision.Parse(args.Length >= 3 ? args[2] : "ANY"); + await Append(context._grpcTestClient.CreateGrpcClient(), stream, expected, + Enumerable.Range(0, count).Select(_ => Event(eventType: "type")), null, context.CancellationToken); + } + + private static async Task WriteFloodWaiting(CommandProcessorContext context, string[] args) + { + var clients = args.Length == 0 ? 1 : MetricPrefixValue.ParseInt(args[0]); + var requests = args.Length == 0 ? 5000 : MetricPrefixValue.ParseLong(args[1]); + var payloadSize = args.Length == 3 ? MetricPrefixValue.ParseInt(args[2]) : 356; + var dataSize = Math.Max(0, payloadSize - 100); + var metadataSize = Math.Min(100, payloadSize); + var data = "DATA" + new string('*', dataSize); + var metadata = "METADATA" + new string('$', metadataSize); + await RunWaitingWriteFlood( + context, + "WRFLW", + FloodWorkload.Create(clients, requests, maxInFlight: 1), + () => [Event(data, metadata)]); + } + + private static async Task MultiWriteFloodWaiting(CommandProcessorContext context, string[] args) + { + var eventCount = args.Length == 0 ? 10 : MetricPrefixValue.ParseInt(args[0]); + var clients = args.Length == 3 ? MetricPrefixValue.ParseInt(args[1]) : 1; + var requests = args.Length == 3 ? MetricPrefixValue.ParseLong(args[2]) : 5000; + await RunWaitingWriteFlood( + context, + "MWRFLW", + FloodWorkload.Create(clients, requests, maxInFlight: 1), + () => Enumerable.Range(0, eventCount).Select(_ => Event(eventType: "type"))); + } + + private static async Task RunWaitingWriteFlood( + CommandProcessorContext context, + string command, + FloodWorkload workload, + Func> createEvents) + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + long successes = 0; + long failures = 0; + await Task.WhenAll(Enumerable.Range(0, workload.ClientCount).Select(async clientIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(); + var stream = $"{command.ToLowerInvariant()}-{clientIndex}-{Guid.NewGuid():N}"; + for (long request = 0; request < workload.RequestsForClient(clientIndex); request++) + { + try + { + await client.AppendToStreamAsync( + stream, + StreamState.Any, + createEvents(), + cancellationToken: context.CancellationToken); + Interlocked.Increment(ref successes); + } + catch (OperationCanceledException) when (context.CancellationToken.IsCancellationRequested) + { + throw; + } + catch + { + Interlocked.Increment(ref failures); + } + } + })); + stopwatch.Stop(); + context.Log.Information( + "Completed. Successes: {successes}, failures: {failures}", + successes, + failures); + LogFloodCompletion(context, command, workload, stopwatch.Elapsed, includeLatency: true); + if (successes != workload.RequestCount) + { + throw new InvalidOperationException("There were errors or not all requests completed."); + } + } + + private static async Task Delete(CommandProcessorContext context, string[] args) + { + var stream = args.Length >= 1 ? args[0] : "test-stream"; + var expected = ExpectedRevision.Parse(args.Length >= 2 ? args[1] : "ANY"); + var client = context._grpcTestClient.CreateGrpcClient(); + if (expected.Revision is { } revision) + { + await client.TombstoneAsync(stream, revision, cancellationToken: context.CancellationToken); + } + else + { + await client.TombstoneAsync(stream, expected.State, cancellationToken: context.CancellationToken); + } + } + + private static async Task Read(CommandProcessorContext context, string[] args) + { + var stream = args.Length >= 1 ? args[0] : "test-stream"; + var eventNumber = args.Length >= 2 ? MetricPrefixValue.ParseLong(args[1]) : 0; + if (eventNumber < -1) + { + throw new ArgumentOutOfRangeException(nameof(args), "Event number cannot be less than -1."); + } + + var readLast = eventNumber == -1; + var direction = readLast ? Direction.Backwards : Direction.Forwards; + var start = readLast ? StreamPosition.End : StreamPosition.FromInt64(eventNumber); + var requireLeader = args.Length >= 3 && bool.Parse(args[2]); + var read = context._grpcTestClient.CreateGrpcClient(requireLeader).ReadStreamAsync(direction, stream, start, + maxCount: 1, cancellationToken: context.CancellationToken); + if (await read.ReadState != ReadState.Ok) + { + throw new InvalidOperationException($"Stream {stream} was not found."); + } + + await foreach (var message in read.Messages.WithCancellation(context.CancellationToken)) + { + if (message is not StreamMessage.Event observed) + { + continue; + } + + context.Log.Information( + "Read event {eventNumber} from {stream}", + observed.ResolvedEvent.Event.EventNumber, + stream); + return; + } + + throw new InvalidOperationException($"Event {eventNumber} was not found in stream {stream}."); + } + + private static async Task ReadFlood(CommandProcessorContext context, string[] args) + { + var clients = args.Length == 0 ? 1 : MetricPrefixValue.ParseInt(args[0]); + var requests = args.Length == 0 ? 5000 : MetricPrefixValue.ParseLong(args[1]); + var streams = args.Length >= 3 ? MetricPrefixValue.ParseInt(args[2]) : 1; + var prefix = args.Length >= 4 ? args[3] : "test-stream"; + var requireLeader = args.Length >= 5 && bool.Parse(args[4]); + if (streams <= 0) + { + throw new ArgumentOutOfRangeException(nameof(args), "Stream count must be positive."); + } + + var workload = FloodWorkload.Create(clients, requests, context._grpcTestClient.Options.ReadWindow); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + long successes = 0; + long failures = 0; + await Task.WhenAll(Enumerable.Range(0, clients).Select(async clientIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(requireLeader); + var streamIndex = streams / clients * clientIndex; + await RunBounded( + workload.RequestsForClient(clientIndex), + workload.MaxInFlightPerClient, + async () => + { + if (await ReadOne( + client, + streams == 1 ? prefix : $"{prefix}-{streamIndex++ % streams}", + context.CancellationToken)) + { + Interlocked.Increment(ref successes); + } + else + { + Interlocked.Increment(ref failures); + } + }); + })); + stopwatch.Stop(); + context.Log.Information( + "Completed. READS succ: {successes}, fail: {failures}.", + successes, + failures); + LogFloodCompletion(context, "RDFL", workload, stopwatch.Elapsed); + if (successes != workload.RequestCount) + { + throw new InvalidOperationException("There were errors or not all requests completed."); + } + } + + private static async Task ReadOne(EventStoreClient client, string stream, CancellationToken cancellationToken) + { + var read = client.ReadStreamAsync( + Direction.Forwards, + stream, + StreamPosition.Start, + maxCount: 1, + cancellationToken: cancellationToken); + return await read.ReadState == ReadState.Ok; + } + + private static async Task ReadAll(CommandProcessorContext context, string[] args) + { + var forwards = args.Length == 0 || args[0].Equals("F", StringComparison.OrdinalIgnoreCase); + var position = forwards ? Position.Start : Position.End; + if (args.Length >= 3) + { + var commitPosition = long.Parse(args[1]); + var preparePosition = long.Parse(args[2]); + if (commitPosition == -1 && preparePosition == -1) + { + position = Position.End; + } + else if (commitPosition < 0 || preparePosition < 0) + { + throw new ArgumentOutOfRangeException(nameof(args), "Read-all positions must both be -1 or non-negative."); + } + else + { + position = new Position((ulong)commitPosition, (ulong)preparePosition); + } + } + var requireLeader = args.Length >= 4 && bool.Parse(args[3]); + + var read = context._grpcTestClient.CreateGrpcClient(requireLeader).ReadAllAsync( + forwards ? Direction.Forwards : Direction.Backwards, + position, + cancellationToken: context.CancellationToken); + await foreach (var _ in read.Messages.WithCancellation(context.CancellationToken)) + { + } + } + + private static async Task WriteLongTerm(CommandProcessorContext context, string[] args) + { + var clients = args.Length == 0 ? 1 : MetricPrefixValue.ParseInt(args[0]); + var minRate = args.Length == 0 ? 1 : MetricPrefixValue.ParseInt(args[1]); + var maxRate = args.Length == 0 ? 2 : MetricPrefixValue.ParseInt(args[2]); + var minutes = args.Length == 0 ? 1 : MetricPrefixValue.ParseInt(args[3]); + var stream = args.Length == 5 ? args[4] : null; + if (clients <= 0 || minRate <= 0 || maxRate < minRate || minutes < 0) + { + throw new ArgumentOutOfRangeException( + nameof(args), + "Clients and request rates must be positive, max rate cannot be below min rate, and duration cannot be negative."); + } + + using var duration = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken); + duration.CancelAfter(TimeSpan.FromMinutes(minutes)); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + long successes = 0; + long failures = 0; + var maxInFlight = Math.Max(1, context._grpcTestClient.Options.WriteWindow / clients); + await Task.WhenAll(Enumerable.Range(0, clients).Select(async clientIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(); + var pending = new List(maxInFlight); + while (!duration.IsCancellationRequested) + { + var rate = minRate == maxRate ? minRate : Random.Shared.Next(minRate, maxRate + 1); + var interval = TimeSpan.FromSeconds(1d / rate); + for (var i = 0; i < rate; i++) + { + if (duration.IsCancellationRequested) + { + break; + } + + var dataSize = Random.Shared.Next(8, 256) * 8; + pending.Add(RecordWrite( + client, + stream ?? $"Stream-{clientIndex % 3}", + Event( + $"DATA {dataSize:00000} {new string('*', dataSize)}", + "METADATA" + new string('$', 100)), + context.CancellationToken)); + if (pending.Count >= maxInFlight) + { + await DrainCompleted(pending); + } + + try + { + await Task.Delay(interval, duration.Token); + } + catch (OperationCanceledException) when (duration.IsCancellationRequested) + { + break; + } + } + } + + await Task.WhenAll(pending); + })); + stopwatch.Stop(); + var requestCount = successes + failures; + var ratePerSecond = stopwatch.ElapsedMilliseconds == 0 + ? 0 + : 1000d * requestCount / stopwatch.ElapsedMilliseconds; + context.Log.Information( + "Completed. Successes: {successes}, failures: {failures}. {requests} requests completed in {elapsed}ms ({rate:0.00} reqs per sec).", + successes, + failures, + requestCount, + stopwatch.ElapsedMilliseconds, + ratePerSecond); + PerfUtils.LogData( + "WRLT", + PerfUtils.Row( + PerfUtils.Col("clientsCnt", clients), + PerfUtils.Col("requestsCnt", requestCount), + PerfUtils.Col("ElapsedMilliseconds", stopwatch.ElapsedMilliseconds)), + PerfUtils.Row(PerfUtils.Col("successes", successes), PerfUtils.Col("failures", failures))); + PerfUtils.LogTeamCityGraphData($"WRLT-{clients}-{requestCount}-reqPerSec", (int)ratePerSecond); + if (requestCount > 0) + { + PerfUtils.LogTeamCityGraphData( + $"WRLT-{clients}-{requestCount}-failureSuccessRate", + (int)(100 * failures / requestCount)); + } + + if (failures > 0) + { + throw new InvalidOperationException($"{failures} long-term write requests failed."); + } + + async Task RecordWrite( + EventStoreClient client, + string targetStream, + EventData eventData, + CancellationToken cancellationToken) + { + try + { + await client.AppendToStreamAsync( + targetStream, + StreamState.Any, + [eventData], + cancellationToken: cancellationToken); + Interlocked.Increment(ref successes); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch + { + Interlocked.Increment(ref failures); + } + } + + static async Task DrainCompleted(List pending) + { + await Task.WhenAny(pending); + for (var index = pending.Count - 1; index >= 0; index--) + { + if (pending[index].IsCompleted) + { + await pending[index]; + pending.RemoveAt(index); + } + } + } + } + + private static async Task Subscribe(CommandProcessorContext context, string[] args) + { + var client = context._grpcTestClient.CreateGrpcClient(); + var subscriptions = new List(); + var dropped = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + if (args.Length == 0) + { + context.Log.Information("Subscribing to all streams over gRPC"); + subscriptions.Add(await client.SubscribeToAllAsync( + FromAll.End, + (_, resolvedEvent, _) => LogSubscriptionEvent(context, resolvedEvent), + subscriptionDropped: (_, reason, exception) => dropped.TrySetResult( + exception ?? new InvalidOperationException($"Subscription dropped: {reason}.")), + cancellationToken: context.CancellationToken)); + } + else + { + foreach (var stream in args) + { + context.Log.Information("Subscribing to stream {stream} over gRPC", stream); + subscriptions.Add(await client.SubscribeToStreamAsync( + stream, + FromStream.End, + (_, resolvedEvent, _) => LogSubscriptionEvent(context, resolvedEvent), + subscriptionDropped: (_, reason, exception) => dropped.TrySetResult( + exception ?? new InvalidOperationException($"Subscription dropped: {reason}.")), + cancellationToken: context.CancellationToken)); + } + } + + context.Log.Information("Subscribed to {subscriptionCount} streams over gRPC", subscriptions.Count); + throw await dropped.Task.WaitAsync(context.CancellationToken); + } + + private static async Task SubscriptionStress(CommandProcessorContext context, string[] args) + { + var count = args.Length == 0 ? 5000 : MetricPrefixValue.ParseInt(args[0]); + var client = context._grpcTestClient.CreateGrpcClient(); + var subscriptions = new List(count); + var dropped = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + long appeared = 0; + var interval = System.Diagnostics.Stopwatch.StartNew(); + for (var i = 0; i < count; i++) + { + subscriptions.Add(await client.SubscribeToStreamAsync($"stream-{i}", FromStream.End, + (_, _, _) => + { + var observed = Interlocked.Increment(ref appeared); + if (observed % 100000 == 0) + { + context.Log.Information( + "Received {eventCount} subscription events at {rate:0.0} events per second", + observed, + 100000 / interval.Elapsed.TotalSeconds); + interval.Restart(); + } + + return Task.CompletedTask; + }, + subscriptionDropped: (_, reason, exception) => dropped.TrySetResult( + exception ?? new InvalidOperationException($"Subscription dropped: {reason}.")), + cancellationToken: context.CancellationToken)); + } + + context.Log.Information("Subscribed to {subscriptionCount} streams over gRPC", subscriptions.Count); + throw await dropped.Task.WaitAsync(context.CancellationToken); + } + + private static Task LogSubscriptionEvent(CommandProcessorContext context, ResolvedEvent resolvedEvent) + { + var @event = resolvedEvent.Event; + context.Log.Information( + "New event on {stream} at {eventNumber}: {eventType}\nData: {data}\nMetadata: {metadata}", + @event.EventStreamId, + @event.EventNumber, + @event.EventType, + Utf8NoBom.GetString(@event.Data.Span), + Utf8NoBom.GetString(@event.Metadata.Span)); + return Task.CompletedTask; + } + + private static async Task Scavenge(CommandProcessorContext context, string[] args) + { + var result = await context._grpcTestClient.CreateOperationsClient() + .StartScavengeAsync(cancellationToken: context.CancellationToken); + context.Log.Information("Scavenge request returned {result}", result); + } + + private static async Task Verify(CommandProcessorContext context, string[] args) + { + var writers = args.Length == 0 ? 20 : MetricPrefixValue.ParseInt(args[0]); + var readers = args.Length == 0 ? 30 : MetricPrefixValue.ParseInt(args[1]); + var events = args.Length == 0 ? 1_000_000 : MetricPrefixValue.ParseInt(args[2]); + var streams = args.Length == 0 ? 1_000 : MetricPrefixValue.ParseInt(args[3]); + var producers = args.Length == 0 ? ["bank"] : args[4] + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (writers <= 0 || readers <= 0 || events < 0 || streams <= 0) + { + throw new ArgumentOutOfRangeException(nameof(args), "Writers, readers, and streams must be positive, and events cannot be negative."); + } + + if (producers.Length != 1 || !producers[0].Equals("bank", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("The gRPC verification workload supports the bank producer.", nameof(args)); + } + + var heads = Enumerable.Repeat(-1, streams).ToArray(); + var streamGates = Enumerable.Range(0, streams).Select(_ => new SemaphoreSlim(1, 1)).ToArray(); + var streamReady = Enumerable.Range(0, streams) + .Select(_ => new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously)) + .ToArray(); + var stopReading = false; + try + { + var writerTasks = Enumerable.Range(0, writers).Select(async writerIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(); + var random = new Random(writerIndex); + var requests = events / writers + (writerIndex == writers - 1 ? events % writers : 0); + for (var request = 0; request < requests; request++) + { + var streamIndex = writers >= streams ? writerIndex % streams : random.Next(streams); + await streamGates[streamIndex].WaitAsync(context.CancellationToken); + try + { + var nextVersion = heads[streamIndex] + 1; + var expected = nextVersion == 0 + ? new ExpectedRevision(StreamState.NoStream, null) + : new ExpectedRevision(default, StreamRevision.FromInt64(nextVersion - 1)); + await Append( + client, + $"account-{streamIndex}", + expected, + [VerificationEventFactory.Create(nextVersion)], + null, + context.CancellationToken); + Volatile.Write(ref heads[streamIndex], nextVersion); + streamReady[streamIndex].TrySetResult(true); + } + finally + { + streamGates[streamIndex].Release(); + } + } + }).ToArray(); + + var readerTasks = Enumerable.Range(0, readers).Select(async readerIndex => + { + var client = context._grpcTestClient.CreateGrpcClient(); + var random = new Random(readerIndex); + while (!Volatile.Read(ref stopReading)) + { + var streamIndex = random.Next(streams); + var head = Volatile.Read(ref heads[streamIndex]); + if (head < 0) + { + await streamReady[streamIndex].Task.WaitAsync(context.CancellationToken); + continue; + } + + await VerifyEventAt( + client, + $"account-{streamIndex}", + random.Next(head + 1), + context.CancellationToken); + } + }).ToArray(); + + try + { + await Task.WhenAll(writerTasks); + } + finally + { + Volatile.Write(ref stopReading, true); + foreach (var ready in streamReady) + { + ready.TrySetResult(true); + } + + await Task.WhenAll(readerTasks); + } + + var verificationClient = context._grpcTestClient.CreateGrpcClient(); + for (var streamIndex = 0; streamIndex < streams; streamIndex++) + { + for (var eventNumber = 0; eventNumber <= heads[streamIndex]; eventNumber++) + { + await VerifyEventAt( + verificationClient, + $"account-{streamIndex}", + eventNumber, + context.CancellationToken); + } + } + } + finally + { + foreach (var gate in streamGates) + { + gate.Dispose(); + } + } + } + + private static async Task VerifyEventAt( + EventStoreClient client, + string stream, + int eventNumber, + CancellationToken cancellationToken) + { + var read = client.ReadStreamAsync( + Direction.Forwards, + stream, + StreamPosition.FromInt64(eventNumber), + maxCount: 1, + cancellationToken: cancellationToken); + await foreach (var message in read.Messages.WithCancellation(cancellationToken)) + { + if (message is not StreamMessage.Event observed) + { + continue; + } + + var expected = VerificationEventFactory.Create(eventNumber); + if (observed.ResolvedEvent.Event.EventType != expected.Type || + !observed.ResolvedEvent.Event.Data.Span.SequenceEqual(expected.Data.Span)) + { + throw new InvalidOperationException($"Event {eventNumber} in {stream} did not match its expected payload."); + } + + return; + } + + throw new InvalidOperationException($"Event {eventNumber} was not found in {stream}."); + } + + private static async Task CheckGrpcSanitization(CommandProcessorContext context, string[] args) + { + using var handler = new HttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + using var client = new HttpClient(handler); + using var request = new HttpRequestMessage(HttpMethod.Post, context._grpcTestClient.HttpEndpoint) + { + Version = HttpVersion.Version20, + VersionPolicy = HttpVersionPolicy.RequestVersionExact, + Content = new ByteArrayContent([0, 0, 0, 0, 16, 1]) + }; + request.RequestUri = new Uri(request.RequestUri, "/event_store.client.streams.Streams/Read"); + request.Content.Headers.ContentType = new("application/grpc"); + using var response = await client.SendAsync(request, context.CancellationToken); + await response.Content.ReadAsByteArrayAsync(context.CancellationToken); + var grpcStatus = response.TrailingHeaders.TryGetValues("grpc-status", out var trailerValues) + ? trailerValues.SingleOrDefault() + : response.Headers.TryGetValues("grpc-status", out var headerValues) + ? headerValues.SingleOrDefault() + : null; + if (response.IsSuccessStatusCode && (grpcStatus is null or "0")) + { + throw new InvalidOperationException("The server accepted a malformed gRPC frame."); + } + } + + private readonly record struct ExpectedRevision(StreamState State, StreamRevision? Revision) + { + public static ExpectedRevision Parse(string value) => value.ToUpperInvariant() switch + { + "ANY" or "-2" => new(StreamState.Any, null), + "NO_STREAM" or "NOSTREAM" or "-1" => new(StreamState.NoStream, null), + _ => new(default, StreamRevision.FromInt64(long.Parse(value))) + }; + } +} + +internal sealed class DelegatingProcessor : ICmdProcessor +{ + private readonly ICmdProcessor _inner; + private readonly Func _translate; + + public DelegatingProcessor(string keyword, string usage, ICmdProcessor inner, Func translate = null) + { + Keyword = keyword; + Usage = usage; + _inner = inner; + _translate = translate ?? (args => args); + } + + public string Keyword { get; } + public string Usage { get; } + + public bool Execute(CommandProcessorContext context, string[] args) => _inner.Execute(context, _translate(args)); +} diff --git a/src/EventStore.TestClient/GrpcCommands/FloodWorkload.cs b/src/EventStore.TestClient/GrpcCommands/FloodWorkload.cs new file mode 100644 index 0000000000..8f11d0792e --- /dev/null +++ b/src/EventStore.TestClient/GrpcCommands/FloodWorkload.cs @@ -0,0 +1,44 @@ +using System; +using EventStore.TestClient.Commands; + +namespace EventStore.TestClient.GrpcCommands; + +internal readonly record struct FloodWorkload( + int ClientCount, + long RequestCount, + int MaxInFlight) +{ + public int MaxInFlightPerClient => Math.Max(1, MaxInFlight / ClientCount); + + public long RequestsForClient(int clientIndex) + { + if (clientIndex < 0 || clientIndex >= ClientCount) + { + throw new ArgumentOutOfRangeException(nameof(clientIndex)); + } + + return RequestCount / ClientCount + (clientIndex == ClientCount - 1 ? RequestCount % ClientCount : 0); + } + + public static FloodWorkload Parse(string[] args, long defaultRequestCount, int maxInFlight) + { + var clientCount = args.Length == 0 ? 1 : MetricPrefixValue.ParseInt(args[0]); + var requestCount = args.Length == 0 ? defaultRequestCount : MetricPrefixValue.ParseLong(args[1]); + return Create(clientCount, requestCount, maxInFlight); + } + + public static FloodWorkload Create(int clientCount, long requestCount, int maxInFlight) + { + if (clientCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(clientCount), "Client count must be positive."); + } + + if (requestCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(requestCount), "Request count cannot be negative."); + } + + return new FloodWorkload(clientCount, requestCount, maxInFlight); + } +} diff --git a/src/EventStore.TestClient/GrpcCommands/ReadAllProcessor.cs b/src/EventStore.TestClient/GrpcCommands/ReadAllProcessor.cs index b053ec564b..3e031e913b 100644 --- a/src/EventStore.TestClient/GrpcCommands/ReadAllProcessor.cs +++ b/src/EventStore.TestClient/GrpcCommands/ReadAllProcessor.cs @@ -22,52 +22,9 @@ public string Keyword public bool Execute(CommandProcessorContext context, string[] args) { - Direction direction = Direction.Forwards; - Position position = Position.Start; - bool forward = true; - bool positionOverridden = false; - int clientCount = 1; - - if (args.Length > 0) - { - if (args.Length > 4) - { - return false; - } - - if (args[0].ToUpper() == "F") - { - forward = true; - } - else if (args[0].ToUpper() == "B") - { - forward = false; - } - else - { - return false; - } - - if (args.Length > 1) - { - clientCount = MetricPrefixValue.ParseInt(args[1]); - } - - if (args.Length == 4) - { - positionOverridden = true; - if (!ulong.TryParse(args[2], out var commitPos) || !ulong.TryParse(args[3], out var preparePos)) - { - return false; - } - - position = new Position(commitPos, preparePos); - } - } - - if (!positionOverridden) + if (!ReadAllWorkload.TryParse(args, out var workload)) { - position = forward ? Position.Start : Position.End; + return false; } context.IsAsync(); @@ -78,25 +35,22 @@ public bool Execute(CommandProcessorContext context, string[] args) return true; } - var task = ReadAll(context, clientCount, direction, position); + var task = ReadAll(context, workload); task.Wait(); return true; } - private async Task ReadAll(CommandProcessorContext context, int clientCount, Direction direction, Position position) + private async Task ReadAll(CommandProcessorContext context, ReadAllWorkload workload) { - - var cts = new CancellationTokenSource(); - ProgressMonitor monitor = new ProgressMonitor(context.Log); var clientTasks = new List(); - for (int i = 0; i < clientCount; i++) + for (int i = 0; i < workload.ClientCount; i++) { if (i > 0) { - await Task.Delay(TimeSpan.FromSeconds(30)); + await Task.Delay(TimeSpan.FromSeconds(30), context.CancellationToken); } EventStoreClient client = context._grpcTestClient.CreateGrpcClient(); @@ -107,13 +61,16 @@ private async Task ReadAll(CommandProcessorContext context, int clientCount, Dir monitor.Stop(); context.Log.Information("=== Reading ALL {readDirection} completed in {elapsed}. Total read: {total}", - direction, monitor.Duration, monitor.Total); + workload.Direction, monitor.Duration, monitor.Total); context.Success(); async Task ReadAllTask(EventStoreClient c) { - var r = c.ReadAllAsync(direction, position, cancellationToken: cts.Token); - await foreach (var _ in r.Messages.WithCancellation(cts.Token)) + var r = c.ReadAllAsync( + workload.Direction, + workload.Position, + cancellationToken: context.CancellationToken); + await foreach (var _ in r.Messages.WithCancellation(context.CancellationToken)) { monitor.Increment(); } diff --git a/src/EventStore.TestClient/GrpcCommands/ReadAllWorkload.cs b/src/EventStore.TestClient/GrpcCommands/ReadAllWorkload.cs new file mode 100644 index 0000000000..46dca5a2a2 --- /dev/null +++ b/src/EventStore.TestClient/GrpcCommands/ReadAllWorkload.cs @@ -0,0 +1,63 @@ +using System; +using EventStore.Client; + +namespace EventStore.TestClient.GrpcCommands; + +internal readonly record struct ReadAllWorkload( + Direction Direction, + Position Position, + int ClientCount) +{ + public static bool TryParse(string[] args, out ReadAllWorkload workload) + { + workload = new ReadAllWorkload(Direction.Forwards, Position.Start, 1); + if (args.Length is not (0 or 1 or 2 or 4)) + { + return false; + } + + if (args.Length == 0) + { + return true; + } + + var direction = args[0].ToUpperInvariant() switch + { + "F" => Direction.Forwards, + "B" => Direction.Backwards, + _ => (Direction?)null + }; + if (direction is null) + { + return false; + } + + try + { + var clientCount = args.Length >= 2 ? MetricPrefixValue.ParseInt(args[1]) : 1; + if (clientCount <= 0) + { + return false; + } + + var position = direction == Direction.Forwards ? Position.Start : Position.End; + if (args.Length == 4) + { + if (!ulong.TryParse(args[2], out var commitPosition) || + !ulong.TryParse(args[3], out var preparePosition)) + { + return false; + } + + position = new Position(commitPosition, preparePosition); + } + + workload = new ReadAllWorkload(direction.Value, position, clientCount); + return true; + } + catch + { + return false; + } + } +} diff --git a/src/EventStore.TestClient/GrpcCommands/VerificationEventFactory.cs b/src/EventStore.TestClient/GrpcCommands/VerificationEventFactory.cs new file mode 100644 index 0000000000..6788a877b0 --- /dev/null +++ b/src/EventStore.TestClient/GrpcCommands/VerificationEventFactory.cs @@ -0,0 +1,46 @@ +using System; +using System.Globalization; +using System.Text; +using EventStore.Client; +using Newtonsoft.Json; + +namespace EventStore.TestClient.GrpcCommands; + +internal static class VerificationEventFactory +{ + private static readonly UTF8Encoding Utf8NoBom = new(false); + + public static EventData Create(int eventNumber) + { + var payload = CreatePayload(eventNumber); + return new EventData( + Uuid.FromGuid(Guid.NewGuid()), + payload.GetType().Name, + Utf8NoBom.GetBytes(JsonConvert.SerializeObject(payload)), + contentType: "application/json"); + } + + private static object CreatePayload(int eventNumber) + { + var internalCounter = eventNumber + 1; + if (internalCounter % 10 == 0) + { + var checkpointCount = internalCounter / 10; + var elementsCount = internalCounter / 2; + return new AccountCheckPoint( + ComputeSum(20, elementsCount, 20) - ComputeSum(100, checkpointCount, 100), + ComputeSum(10, elementsCount, 20)); + } + + return internalCounter % 2 == 0 + ? new AccountCredited(internalCounter * 10, (internalCounter % 17).ToString(CultureInfo.InvariantCulture)) + : new AccountDebited(internalCounter * 10, (internalCounter % 17).ToString(CultureInfo.InvariantCulture)); + } + + private static int ComputeSum(int first, int count, int step) => + count * (2 * first + step * (count - 1)) / 2; + + private sealed record AccountCredited(decimal CreditedAmount, string Kind); + private sealed record AccountDebited(decimal DebitedAmount, string Kind); + private sealed record AccountCheckPoint(decimal CreditedAmount, decimal DebitedAmount); +} diff --git a/src/EventStore.TestClient/GrpcCommands/WriteFloodProcessor.cs b/src/EventStore.TestClient/GrpcCommands/WriteFloodProcessor.cs index a2ebd98a7f..5e191a2248 100644 --- a/src/EventStore.TestClient/GrpcCommands/WriteFloodProcessor.cs +++ b/src/EventStore.TestClient/GrpcCommands/WriteFloodProcessor.cs @@ -110,7 +110,7 @@ private async Task WriteFlood(CommandProcessorContext context, WriteFloodStats s var start = new TaskCompletionSource(); stats.StartTime = DateTime.UtcNow; var sw2 = new Stopwatch(); - var capacity = 2000 / clientsCnt; + var capacity = Math.Max(1, context._grpcTestClient.Options.WriteWindow / clientsCnt); var clientTasks = new List(); for (int i = 0; i < clientsCnt; i++) { diff --git a/src/EventStore.TestClient/GrpcTestClient.cs b/src/EventStore.TestClient/GrpcTestClient.cs index b2bb9fa793..2c3a90e51b 100644 --- a/src/EventStore.TestClient/GrpcTestClient.cs +++ b/src/EventStore.TestClient/GrpcTestClient.cs @@ -1,6 +1,5 @@ using System; using EventStore.Client; -using Connection = EventStore.Transport.Tcp.TcpTypedConnection; using ILogger = Serilog.ILogger; namespace EventStore.TestClient; @@ -10,8 +9,9 @@ namespace EventStore.TestClient; /// public class GrpcTestClient { - private ClientOptions _options; - private ILogger _log; + private readonly ClientOptions _options; + private readonly ILogger _log; + private readonly Func _settingsFactory; /// /// Constructs a new @@ -19,9 +19,18 @@ public class GrpcTestClient /// /// public GrpcTestClient(ClientOptions options, ILogger log) + : this(options, log, null) + { + } + + internal GrpcTestClient( + ClientOptions options, + ILogger log, + Func settingsFactory) { _options = options; _log = log; + _settingsFactory = settingsFactory; } /// @@ -29,9 +38,27 @@ public GrpcTestClient(ClientOptions options, ILogger log) /// /// public EventStoreClient CreateGrpcClient() + => CreateGrpcClient(requireLeader: true); + + internal EventStoreClient CreateGrpcClient(bool requireLeader) { - _log.Debug("Creating gRPC client with connection string '{connectionString}'.", ConnectionString); - return new EventStoreClient(Settings); + _log.Debug("Creating gRPC client."); + return new EventStoreClient(CreateClientSettings(requireLeader)); + } + + internal EventStoreClientSettings CreateClientSettings(bool requireLeader) + { + var settings = Settings; + settings.ConnectivitySettings.NodePreference = requireLeader + ? NodePreference.Leader + : NodePreference.Random; + return settings; + } + + internal EventStoreOperationsClient CreateOperationsClient() + { + _log.Debug("Creating gRPC operations client."); + return new EventStoreOperationsClient(Settings); } /// @@ -41,7 +68,12 @@ public EventStoreClient CreateGrpcClient() string.IsNullOrWhiteSpace(Settings.DefaultCredentials?.Username) || string.IsNullOrWhiteSpace(Settings.DefaultCredentials?.Password); - private EventStoreClientSettings Settings => EventStoreClientSettings.Create(ConnectionString); + internal EventStoreClientSettings Settings => + _settingsFactory?.Invoke() ?? EventStoreClientSettings.Create(ConnectionString); + internal ClientOptions Options => _options; + + internal Uri HttpEndpoint => Settings.ConnectivitySettings.Address ?? + throw new InvalidOperationException("CHKGRPC requires a single-node connection string."); private string ConnectionString => string.IsNullOrWhiteSpace(_options.ConnectionString) ? $"esdb://{_options.Host}:{_options.HttpPort}?tls={_options.UseTls}&tlsVerifyCert={_options.TlsValidateServer}" diff --git a/src/EventStore.TestClient/PortsHelper.cs b/src/EventStore.TestClient/PortsHelper.cs deleted file mode 100644 index ad9ff9c688..0000000000 --- a/src/EventStore.TestClient/PortsHelper.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Threading.Tasks; -using ILogger = Serilog.ILogger; - -namespace EventStore.TestClient; - -internal static class PortsHelper -{ - private static readonly ILogger Log = - Serilog.Log.ForContext(Serilog.Core.Constants.SourceContextPropertyName, "PortsHelper"); - - public const int PortStart = 45000; - public const int PortCount = 200; - - private static readonly ConcurrentQueue AvailablePorts = - new ConcurrentQueue(Enumerable.Range(PortStart, PortCount)); - - public static void InitPorts(IPAddress ip) - { - var sw = Stopwatch.StartNew(); - - int p; - while (AvailablePorts.TryDequeue(out p)) - { - } - - Log.Verbose("PortsHelper: starting to examine ports at [{ip}].", ip); - - int succ = 0; - for (int port = PortStart; port < PortStart + PortCount; ++port) - { - try - { - var listener = new TcpListener(ip, port); - listener.Start(); - listener.Stop(); - } - catch (Exception exc) - { - Log.Verbose(exc, "PortsHelper: port {port} unavailable for TcpListener. Error: {e}.", port, - exc.Message); - continue; - } - - try - { - var httpListener = new HttpListener(); - httpListener.Prefixes.Add(string.Format("http://+:{0}/", port)); - httpListener.Start(); - - Exception httpListenerError = null; - var listenTask = Task.Factory.StartNew(() => - { - try - { - var context = httpListener.GetContext(); - context.Response.Close(new byte[] { 1, 2, 3 }, true); - } - catch (Exception exc) - { - httpListenerError = exc; - } - }); - var client = new HttpClient(); - var request = client.GetAsync(string.Format("http://{0}:{1}/", ip, port)).Result; - var buffer = new byte[256]; - var read = request.Content.ReadAsStream().Read(buffer, 0, buffer.Length); - if (read != 3 || buffer[0] != 1 || buffer[1] != 2 || buffer[2] != 3) - { - throw new Exception(string.Format("Unexpected response received from HTTP on port {0}.", port)); - } - - if (!listenTask.Wait(5000)) - { - throw new Exception("PortsHelper: time out waiting for HttpListener to return."); - } - - if (httpListenerError != null) - { - throw httpListenerError; - } - - httpListener.Stop(); - } - catch (Exception exc) - { - Log.Verbose(exc, "PortsHelper: port {port} unavailable for HttpListener. Error: {e}.", port, - exc.Message); - continue; - } - - AvailablePorts.Enqueue(port); - succ += 1; - } - - Log.Verbose("PortsHelper: {ports} ports are available at [{ip}].", succ, ip); - if (succ <= PortCount / 2) - { - throw new Exception("More than half requested ports are unavailable."); - } - - Log.Verbose("PortsHelper: test took {elapsed}.", sw.Elapsed); - } - - public static int GetAvailablePort(IPAddress ip) - { - for (int i = 0; i < 50; ++i) - { - int port; - if (!AvailablePorts.TryDequeue(out port)) - { - throw new Exception("Couldn't get free TCP port for MiniNode."); - } - - /* - try - { - var listener = new TcpListener(ip, port); - listener.Start(); - listener.Stop(); - } - catch (Exception) - { - AvailablePorts.Enqueue(port); - continue; - } - */ - - try - { - var httpListener = new HttpListener(); - httpListener.Prefixes.Add(string.Format("http://127.0.0.1:{0}/", port)); - httpListener.Start(); - httpListener.Stop(); - } - catch (Exception) - { - AvailablePorts.Enqueue(port); - continue; - // throw new Exception( - // string.Format("HttpListener couldn't listen on port {0}, but TcpListener was OK.\nError: {1}", port, exc), exc); - } - - return port; - } - - throw new Exception("Reached trials limit while trying to get free port for MiniNode"); - } - - public static void ReturnPort(int port) - { - AvailablePorts.Enqueue(port); - } - - /* - private static int[] GetRandomPorts(int from, int portCount) - { - var res = new int[portCount]; - var rnd = new Random(Math.Abs(Guid.NewGuid().GetHashCode())); - for (int i = 0; i < portCount; ++i) - { - res[i] = from + i; - } - for (int i = 0; i < portCount; ++i) - { - int index = rnd.Next(portCount - i); - int tmp = res[i]; - res[i] = res[i + index]; - res[i + index] = tmp; - } - return res; - } - */ -} diff --git a/src/EventStore.TestClient/Program.cs b/src/EventStore.TestClient/Program.cs index 942b00c305..eee7b0210a 100644 --- a/src/EventStore.TestClient/Program.cs +++ b/src/EventStore.TestClient/Program.cs @@ -29,22 +29,17 @@ internal static class Program /// Print effective configuration to console and then exit. /// /// The host to bind to. - /// The port to run the TCP server on. /// The port to run the HTTP server on. /// - /// - /// - /// - /// /// /// - /// The connection string to use when connecting to the server. Not used by the raw TCP client + /// The connection string to use when connecting to the server. /// The format for the stats log. /// public static async Task Main(bool version = false, FileInfo? log = null, bool whatIf = false, - string[]? command = null, string host = "localhost", int tcpPort = 1113, int httpPort = 2113, - int timeout = Timeout.Infinite, int readWindow = 2000, int writeWindow = 2000, int pingWindow = 2000, - bool reconnect = true, bool useTls = false, bool tlsValidateServer = false, string connectionString = "", + string[]? command = null, string host = "localhost", int httpPort = 2113, + int timeout = Timeout.Infinite, + bool useTls = false, bool tlsValidateServer = false, string connectionString = "", StatsFormat statsFormat = StatsFormat.Csv) { Log.Logger = EventStoreLoggerConfiguration.ConsoleLog; @@ -63,11 +58,6 @@ public static async Task Main(bool version = false, FileInfo? log = null, b Timeout = timeout, HttpPort = httpPort, Host = host, - TcpPort = tcpPort, - Reconnect = reconnect, - PingWindow = pingWindow, - ReadWindow = readWindow, - WriteWindow = writeWindow, UseTls = useTls, TlsValidateServer = tlsValidateServer, ConnectionString = connectionString, diff --git a/src/EventStore.TestClient/Properties/AssemblyInfo.cs b/src/EventStore.TestClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..85ca1f9e4c --- /dev/null +++ b/src/EventStore.TestClient/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("EventStore.TestClient.Tests")] diff --git a/src/EventStore.TestClient/TcpTestClient.cs b/src/EventStore.TestClient/TcpTestClient.cs deleted file mode 100644 index 2b4470a897..0000000000 --- a/src/EventStore.TestClient/TcpTestClient.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Threading; -using EventStore.BufferManagement; -using EventStore.Common.Utils; -using EventStore.Core.Services.Transport.Tcp; -using EventStore.Transport.Tcp; -using EventStore.Transport.Tcp.Formatting; -using EventStore.Transport.Tcp.Framing; -using Connection = EventStore.Transport.Tcp.TcpTypedConnection; -using ILogger = Serilog.ILogger; - -namespace EventStore.TestClient; - -/// -/// A test client that connects over Raw TCP connections -/// -public class TcpTestClient -{ - private readonly BufferManager _bufferManager = - new BufferManager(TcpConfiguration.BufferChunksCount, TcpConfiguration.SocketBufferSize); - - private readonly TcpClientConnector _connector = new TcpClientConnector(); - - private readonly bool _validateServer; - private readonly bool _useSsl; - private readonly ILogger _log; - private readonly bool _interactiveMode; - /// - /// The TCP EndPoint of the Event Store node - /// - public readonly EndPoint TcpEndpoint; - /// - /// The Client Options used to start EventStore.TestClient - /// - public readonly ClientOptions Options; - - /// - /// Constructs a new - /// - /// - /// - /// - public TcpTestClient(ClientOptions options, bool interactiveMode, ILogger log) - { - _interactiveMode = interactiveMode; - _log = log; - _useSsl = options.UseTls; - TcpEndpoint = new DnsEndPoint(options.Host, options.TcpPort); - _validateServer = options.TlsValidateServer; - Options = options; - } - - /// - /// Creates a new raw TCP connection - /// - /// - /// - /// - /// - /// - /// - /// - /// - public Connection CreateTcpConnection(CommandProcessorContext context, - Action handlePackage, - Action connectionEstablished = null, - Action connectionClosed = null, - bool failContextOnError = true, - IPEndPoint tcpEndPoint = null) - { - var connectionCreatedEvent = new ManualResetEventSlim(false); - Connection typedConnection = null; - - Action onConnectionEstablished = conn => - { - // we execute callback on ThreadPool because on FreeBSD it can be called synchronously - // causing deadlock - ThreadPool.QueueUserWorkItem(_ => - { - if (!_interactiveMode) - { - _log.Information( - "TcpTypedConnection: connected to [{remoteEndPoint}, L{localEndPoint}, {connectionId:B}].", - conn.RemoteEndPoint, conn.LocalEndPoint, conn.ConnectionId); - } - - if (connectionEstablished != null) - { - if (!connectionCreatedEvent.Wait(10000)) - { - throw new Exception("TcpTypedConnection: creation took too long!"); - } - - connectionEstablished(typedConnection); - } - }); - }; - Action onConnectionFailed = (conn, error) => - { - _log.Error( - "TcpTypedConnection: connection to [{remoteEndPoint}, L{localEndPoint}, {connectionId:B}] failed. Error: {e}.", - conn.RemoteEndPoint, conn.LocalEndPoint, conn.ConnectionId, error); - - if (connectionClosed != null) - { - connectionClosed(null, error); - } - - if (failContextOnError) - { - context.Fail(reason: string.Format("Socket connection failed with error {0}.", error)); - } - }; - - var endpoint = tcpEndPoint ?? TcpEndpoint; - ITcpConnection connection; - if (_useSsl) - { - connection = _connector.ConnectSslTo( - Guid.NewGuid(), - endpoint.GetHost(), - endpoint.GetOtherNames(), - endpoint.ResolveDnsToIPAddress(), - TcpConnectionManager.ConnectionTimeout, - (_, _, err, _) => (err == SslPolicyErrors.None || !_validateServer, err.ToString()), - () => null, - onConnectionEstablished, - onConnectionFailed, - verbose: false); - } - else - { - connection = _connector.ConnectTo( - Guid.NewGuid(), - endpoint.ResolveDnsToIPAddress(), - TcpConnectionManager.ConnectionTimeout, - onConnectionEstablished, - onConnectionFailed, - verbose: false); - } - - typedConnection = new Connection(connection, new RawMessageFormatter(_bufferManager), - new LengthPrefixMessageFramer()); - typedConnection.ConnectionClosed += - (conn, error) => - { - if (!_interactiveMode || error != SocketError.Success) - { - _log.Information( - "TcpTypedConnection: connection [{remoteEndPoint}, L{localEndPoint}] was closed {status}", - conn.RemoteEndPoint, conn.LocalEndPoint, - error == SocketError.Success ? "cleanly." : "with error: " + error + "."); - } - - if (connectionClosed != null) - { - connectionClosed(conn, error); - } - else - { - _log.Information("connectionClosed callback was null"); - } - }; - connectionCreatedEvent.Set(); - - typedConnection.ReceiveAsync( - (conn, pkg) => - { - var package = new TcpPackage(); - bool validPackage = false; - try - { - package = TcpPackage.FromArraySegment(new ArraySegment(pkg)); - validPackage = true; - - if (package.Command == TcpCommand.HeartbeatRequestCommand) - { - var resp = new TcpPackage(TcpCommand.HeartbeatResponseCommand, Guid.NewGuid(), null); - conn.EnqueueSend(resp.AsByteArray()); - return; - } - - handlePackage(conn, package); - } - catch (Exception ex) - { - _log.Information(ex, - "TcpTypedConnection: [{remoteEndPoint}, L{localEndPoint}] ERROR for {package}. Connection will be closed.", - conn.RemoteEndPoint, conn.LocalEndPoint, - validPackage ? package.Command as object : ""); - conn.Close(ex.Message); - - if (failContextOnError) - { - context.Fail(ex); - } - } - }); - - return typedConnection; - } -} diff --git a/src/EventStore.sln b/src/EventStore.sln index 7262d86925..ca8cbfa04b 100644 --- a/src/EventStore.sln +++ b/src/EventStore.sln @@ -63,6 +63,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{CB56B2BD EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrogonEventStore.SemanticConventions", "TrogonEventStore.SemanticConventions\TrogonEventStore.SemanticConventions.csproj", "{05D9513D-A26A-410A-A145-4A17548EB79C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStore.TestClient.Tests", "EventStore.TestClient.Tests\EventStore.TestClient.Tests.csproj", "{0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -349,6 +351,18 @@ Global {05D9513D-A26A-410A-A145-4A17548EB79C}.Release|ARM64.Build.0 = Release|Any CPU {05D9513D-A26A-410A-A145-4A17548EB79C}.Release|x64.ActiveCfg = Release|Any CPU {05D9513D-A26A-410A-A145-4A17548EB79C}.Release|x64.Build.0 = Release|Any CPU + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Debug|ARM64.Build.0 = Debug|ARM64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Debug|x64.ActiveCfg = Debug|x64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Debug|x64.Build.0 = Debug|x64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Release|Any CPU.Build.0 = Release|Any CPU + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Release|ARM64.ActiveCfg = Release|ARM64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Release|ARM64.Build.0 = Release|ARM64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Release|x64.ActiveCfg = Release|x64 + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -363,6 +377,7 @@ Global {58219323-BD46-4F67-A83A-410F56F84EE3} = {CB56B2BD-5ABA-49C7-BD57-21B9CD5C3205} {41EEB66F-D77B-4FF3-BF68-DA3748C80BB5} = {CB56B2BD-5ABA-49C7-BD57-21B9CD5C3205} {CA966906-51C8-4520-924E-988FA396B712} = {CB56B2BD-5ABA-49C7-BD57-21B9CD5C3205} + {0ADF086C-FE82-4AB9-91E3-ABDDC4DDC9B4} = {CB56B2BD-5ABA-49C7-BD57-21B9CD5C3205} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CD63E89D-F4FA-42A7-A10B-50CF854078FA}