diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/BinaryEncodingEnabledBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/BinaryEncodingEnabledBenchmark.cs new file mode 100644 index 0000000000..5b83958392 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/BinaryEncodingEnabledBenchmark.cs @@ -0,0 +1,247 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Benchmarks +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Threading.Tasks; + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Columns; + using BenchmarkDotNet.Configs; + using BenchmarkDotNet.Diagnosers; + using BenchmarkDotNet.Exporters.Csv; + using BenchmarkDotNet.Exporters; + using BenchmarkDotNet.Jobs; + using Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks; + using Newtonsoft.Json; + using Microsoft.Azure.Cosmos.Json; + + [MemoryDiagnoser] + [BenchmarkCategory("GateBenchmark")] + [Config(typeof(BinaryEncodingEnabledBenchmark.CustomBenchmarkConfig))] + public class BinaryEncodingEnabledBenchmark + { + private MockedItemBenchmarkHelper benchmarkHelper; + private ItemRequestOptions requestOptions; + private Container container; + + [Params(true, false)] + public bool EnableBinaryResponseOnPointOperations; + + [GlobalSetup] + public async Task GlobalSetupAsync() + { + Environment.SetEnvironmentVariable("COSMOS_ENABLE_BINARY_ENCODING", this.EnableBinaryResponseOnPointOperations.ToString()); + + JsonSerializationFormat serializationFormat = this.EnableBinaryResponseOnPointOperations + ? JsonSerializationFormat.Binary + : JsonSerializationFormat.Text; + + this.benchmarkHelper = new MockedItemBenchmarkHelper(serializationFormat: serializationFormat); + this.container = this.benchmarkHelper.TestContainer; + + this.requestOptions = new ItemRequestOptions + { + EnableBinaryResponseOnPointOperations = this.EnableBinaryResponseOnPointOperations + }; + + // Create the item in the container + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.container.CreateItemStreamAsync( + ms, + new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if ((int)response.StatusCode > 300 || response.Content == null) + { + throw new InvalidOperationException($"Failed to create item with status code {response.StatusCode}"); + } + } + } + + + [Benchmark] + public async Task CreateItemAsync() + { + ItemResponse itemResponse = await this.container.CreateItemAsync( + item: this.benchmarkHelper.TestItem, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (itemResponse.StatusCode != HttpStatusCode.Created && itemResponse.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {this.benchmarkHelper.TestItem.id} was not created."); + } + } + + [Benchmark] + public async Task CreateItemStreamAsync() + { + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.container.CreateItemStreamAsync( + ms, + new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + this.requestOptions)) + { + if ((int)response.StatusCode > 300 || response.Content == null) + { + throw new InvalidOperationException($"Item {this.benchmarkHelper.TestItem.id} was not created stream."); + } + } + } + + [Benchmark] + public async Task ReadItemAsync() + { + ItemResponse itemResponse = await this.container.ReadItemAsync( + id: MockedItemBenchmarkHelper.ExistingItemId, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (itemResponse.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {MockedItemBenchmarkHelper.ExistingItemId} was not read."); + } + } + + [Benchmark] + public async Task ReadItemStreamAsync() + { + ResponseMessage response = await this.container.ReadItemStreamAsync( + id: MockedItemBenchmarkHelper.ExistingItemId, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {MockedItemBenchmarkHelper.ExistingItemId} was not read stream."); + } + } + + [Benchmark] + public async Task UpsertItemAsync() + { + ItemResponse itemResponse = await this.container.UpsertItemAsync( + item: this.benchmarkHelper.TestItem, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (itemResponse.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {this.benchmarkHelper.TestItem.id} was not upserted."); + } + } + + [Benchmark] + public async Task UpsertItemStreamAsync() + { + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.container.UpsertItemStreamAsync( + ms, + new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + this.requestOptions)) + { + if ((int)response.StatusCode > 300 || response.Content == null) + { + throw new InvalidOperationException($"Item {this.benchmarkHelper.TestItem.id} was not upserted stream."); + } + } + } + + [Benchmark] + public async Task ReplaceItemAsync() + { + ItemResponse itemResponse = await this.container.ReplaceItemAsync( + item: this.benchmarkHelper.TestItem, + id: MockedItemBenchmarkHelper.ExistingItemId, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (itemResponse.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {this.benchmarkHelper.TestItem.id} was not replaced."); + } + } + + [Benchmark] + public async Task ReplaceItemStreamAsync() + { + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.container.ReplaceItemStreamAsync( + ms, + MockedItemBenchmarkHelper.ExistingItemId, + new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + this.requestOptions)) + { + if (response.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {this.benchmarkHelper.TestItem.id} was not replaced stream."); + } + } + } + + [Benchmark] + public async Task DeleteItemAsync() + { + ItemResponse itemResponse = await this.container.DeleteItemAsync( + id: MockedItemBenchmarkHelper.ExistingItemId, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (itemResponse.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {MockedItemBenchmarkHelper.ExistingItemId} was not deleted."); + } + } + + [Benchmark] + public async Task DeleteItemStreamAsync() + { + ResponseMessage response = await this.container.DeleteItemStreamAsync( + id: MockedItemBenchmarkHelper.ExistingItemId, + partitionKey: new PartitionKey(MockedItemBenchmarkHelper.ExistingItemId), + requestOptions: this.requestOptions); + + if (response.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException($"Item {MockedItemBenchmarkHelper.ExistingItemId} was not deleted stream."); + } + } + + [GlobalCleanup] + public void Cleanup() + { + // Restore the environment variable to its original value + Environment.SetEnvironmentVariable("COSMOS_ENABLE_BINARY_ENCODING", "false"); + } + + private class CustomBenchmarkConfig : ManualConfig + { + public CustomBenchmarkConfig() + { + this.AddColumn(StatisticColumn.OperationsPerSecond); + this.AddColumn(StatisticColumn.Q3); + this.AddColumn(StatisticColumn.P80); + this.AddColumn(StatisticColumn.P85); + this.AddColumn(StatisticColumn.P90); + this.AddColumn(StatisticColumn.P95); + this.AddColumn(StatisticColumn.P100); + + this.AddDiagnoser(MemoryDiagnoser.Default); + this.AddDiagnoser(ThreadingDiagnoser.Default); + this.AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); + + // Minimal run to reduce time + this.AddJob(Job.ShortRun + .WithStrategy(BenchmarkDotNet.Engines.RunStrategy.Throughput)); + + this.AddExporter(HtmlExporter.Default); + this.AddExporter(CsvExporter.Default); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs index c3d473f079..5cc097b211 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Benchmarks/MockedItemBenchmarkHelper.cs @@ -6,8 +6,11 @@ namespace Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks { using System; using System.IO; + using System.Text; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.Json; + using Microsoft.Azure.Cosmos.Json.Interop; using Newtonsoft.Json; /// @@ -24,42 +27,63 @@ public class MockedItemBenchmarkHelper internal ToDoActivity TestItem { get; } internal CosmosClient TestClient { get; } internal Container TestContainer { get; } - + internal byte[] TestItemBytes { get; } + internal JsonSerializationFormat SerializationFormat { get; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public MockedItemBenchmarkHelper( + internal MockedItemBenchmarkHelper( bool useCustomSerializer = false, bool includeDiagnosticsToString = false, bool useBulk = false, bool isDistributedTracingEnabled = false, - bool isClientMetricsEnabled = false) + bool isClientMetricsEnabled = false, + JsonSerializationFormat serializationFormat = JsonSerializationFormat.Text) { - this.TestClient = MockDocumentClient.CreateMockCosmosClient(useCustomSerializer, + this.TestClient = MockDocumentClient.CreateMockCosmosClient( + useCustomSerializer, (builder) => builder - .WithBulkExecution(useBulk) - .WithClientTelemetryOptions(new CosmosClientTelemetryOptions() - { - DisableDistributedTracing = !isDistributedTracingEnabled, - IsClientMetricsEnabled = isClientMetricsEnabled - })); + .WithBulkExecution(useBulk) + .WithClientTelemetryOptions(new CosmosClientTelemetryOptions() + { + DisableDistributedTracing = !isDistributedTracingEnabled, + IsClientMetricsEnabled = isClientMetricsEnabled + })); this.TestContainer = this.TestClient.GetDatabase("myDB").GetContainer("myColl"); this.IncludeDiagnosticsToString = includeDiagnosticsToString; + this.SerializationFormat = serializationFormat; + + // Load the test item from the JSON file + string payloadContent = File.ReadAllText("samplepayload.json"); + this.TestItem = JsonConvert.DeserializeObject(payloadContent); - using (FileStream tmp = File.OpenRead("samplepayload.json")) - using (MemoryStream ms = new MemoryStream()) + // Serialize TestItem into the desired format (Text or Binary) + if (this.SerializationFormat == JsonSerializationFormat.Binary) { - tmp.CopyTo(ms); - this.TestItemBytes = ms.ToArray(); + using (CosmosDBToNewtonsoftWriter writer = new CosmosDBToNewtonsoftWriter(JsonSerializationFormat.Binary)) + { + writer.Formatting = Newtonsoft.Json.Formatting.None; + Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); + serializer.Serialize(writer, this.TestItem); + this.TestItemBytes = writer.GetResult().ToArray(); + } } - - using (MemoryStream ms = new MemoryStream(this.TestItemBytes)) + else { - string payloadContent = File.ReadAllText("samplepayload.json"); - this.TestItem = JsonConvert.DeserializeObject(payloadContent); + using (MemoryStream ms = new MemoryStream()) + using (StreamWriter sw = new StreamWriter(ms, new UTF8Encoding(false, true), 1024, true)) + using (Newtonsoft.Json.JsonWriter writer = new Newtonsoft.Json.JsonTextWriter(sw)) + { + writer.Formatting = Newtonsoft.Json.Formatting.None; + Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); + serializer.Serialize(writer, this.TestItem); + writer.Flush(); + sw.Flush(); + this.TestItemBytes = ms.ToArray(); + } } } @@ -74,7 +98,7 @@ public void IncludeDiagnosticToStringHelper( string diagnostics = cosmosDiagnostics.ToString(); if (string.IsNullOrEmpty(diagnostics)) { - throw new Exception(); + throw new Exception("Diagnostics string is empty."); } } @@ -88,4 +112,4 @@ public MemoryStream GetItemPayloadAsStream() publiclyVisible: true); } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json index 07f670b0b6..ce97da0014 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/BenchmarkResults.json @@ -90,5 +90,25 @@ "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithClientMetricsEnabled]": "1206121.5", "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": "1204822", "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithDistributedTracingEnabled]": "1208600.25", - "MockedItemBulkBenchmark.UpsertItem;[Type=Stream]": "768928.75" + "MockedItemBulkBenchmark.UpsertItem;[Type=Stream]": "768928.75", + "BinaryEncodingEnabledBenchmark.CreateItemAsync;[EnableBinaryResponseOnPointOperations=True]": "38127.25", + "BinaryEncodingEnabledBenchmark.CreateItemStreamAsync;[EnableBinaryResponseOnPointOperations=True]": "26697.5", + "BinaryEncodingEnabledBenchmark.ReadItemAsync;[EnableBinaryResponseOnPointOperations=True]": "34509.75", + "BinaryEncodingEnabledBenchmark.ReadItemStreamAsync;[EnableBinaryResponseOnPointOperations=True]": "26752.5", + "BinaryEncodingEnabledBenchmark.UpsertItemAsync;[EnableBinaryResponseOnPointOperations=True]": "38337.25", + "BinaryEncodingEnabledBenchmark.UpsertItemStreamAsync;[EnableBinaryResponseOnPointOperations=True]": "26919.25", + "BinaryEncodingEnabledBenchmark.ReplaceItemAsync;[EnableBinaryResponseOnPointOperations=True]": "38345", + "BinaryEncodingEnabledBenchmark.ReplaceItemStreamAsync;[EnableBinaryResponseOnPointOperations=True]": "26924.5", + "BinaryEncodingEnabledBenchmark.DeleteItemAsync;[EnableBinaryResponseOnPointOperations=True]": "33015.25", + "BinaryEncodingEnabledBenchmark.DeleteItemStreamAsync;[EnableBinaryResponseOnPointOperations=True]": "25829.5", + "BinaryEncodingEnabledBenchmark.CreateItemAsync;[EnableBinaryResponseOnPointOperations=False]": "38127.25", + "BinaryEncodingEnabledBenchmark.CreateItemStreamAsync;[EnableBinaryResponseOnPointOperations=False]": "26697.5", + "BinaryEncodingEnabledBenchmark.ReadItemAsync;[EnableBinaryResponseOnPointOperations=False]": "34509.75", + "BinaryEncodingEnabledBenchmark.ReadItemStreamAsync;[EnableBinaryResponseOnPointOperations=False]": "26752.5", + "BinaryEncodingEnabledBenchmark.UpsertItemAsync;[EnableBinaryResponseOnPointOperations=False]": "38337.25", + "BinaryEncodingEnabledBenchmark.UpsertItemStreamAsync;[EnableBinaryResponseOnPointOperations=False]": "26919.25", + "BinaryEncodingEnabledBenchmark.ReplaceItemAsync;[EnableBinaryResponseOnPointOperations=False]": "38345", + "BinaryEncodingEnabledBenchmark.ReplaceItemStreamAsync;[EnableBinaryResponseOnPointOperations=False]": "26924.5", + "BinaryEncodingEnabledBenchmark.DeleteItemAsync;[EnableBinaryResponseOnPointOperations=False]": "33015.25", + "BinaryEncodingEnabledBenchmark.DeleteItemStreamAsync;[EnableBinaryResponseOnPointOperations=False]": "25829.5" } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockRequestHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockRequestHelper.cs index ae7a34e233..253de92d0a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockRequestHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockRequestHelper.cs @@ -10,6 +10,8 @@ namespace Microsoft.Azure.Cosmos.Performance.Tests using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Text; + using Microsoft.Azure.Cosmos.Json; + using Microsoft.Azure.Cosmos.Json.Interop; using Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Collections; @@ -25,6 +27,7 @@ internal static class MockRequestHelper internal static readonly byte[] notFoundPayload = Encoding.ASCII.GetBytes("{\"Errors\":[\"Resource Not Found.Learn more: https:\\/\\/ aka.ms\\/ cosmosdb - tsg - not - found\"]}"); + private static readonly string BinarySerializationFormat = SupportedSerializationFormats.CosmosBinary.ToString(); static MockRequestHelper() { @@ -66,76 +69,95 @@ public static DocumentServiceResponse GetDocumentServiceResponse(DocumentService { StoreResponseNameValueCollection headers = MockRequestHelper.GenerateTestHeaders(); + DocumentServiceResponse response; if (request.OperationType == OperationType.Read) { +#pragma warning disable IDE0045 // Convert to conditional expression if (request.ResourceAddress.EndsWith(MockedItemBenchmarkHelper.ExistingItemId)) { - return new DocumentServiceResponse( + response = new DocumentServiceResponse( new MemoryStream(MockRequestHelper.testItemResponsePayload), headers, System.Net.HttpStatusCode.OK ); } - - return new DocumentServiceResponse( - new MemoryStream(MockRequestHelper.notFoundPayload), - headers, - System.Net.HttpStatusCode.NotFound - ); + else + { + response = new DocumentServiceResponse( + new MemoryStream(MockRequestHelper.notFoundPayload), + headers, + System.Net.HttpStatusCode.NotFound + ); + } +#pragma warning restore IDE0045 // Convert to conditional expression } - - if (request.OperationType == OperationType.Delete) + else if (request.OperationType == OperationType.Delete) { +#pragma warning disable IDE0045 // Convert to conditional expression if (request.ResourceAddress.EndsWith(MockedItemBenchmarkHelper.ExistingItemId)) { - return new DocumentServiceResponse( + response = new DocumentServiceResponse( new MemoryStream(MockRequestHelper.testItemResponsePayload), headers, System.Net.HttpStatusCode.OK ); } - - return new DocumentServiceResponse( - new MemoryStream(MockRequestHelper.notFoundPayload), - headers, - System.Net.HttpStatusCode.NotFound - ); + else + { + response = new DocumentServiceResponse( + new MemoryStream(MockRequestHelper.notFoundPayload), + headers, + System.Net.HttpStatusCode.NotFound + ); + } +#pragma warning restore IDE0045 // Convert to conditional expression } - - if (request.OperationType == OperationType.Create + else if (request.OperationType == OperationType.Create || request.OperationType == OperationType.Replace || request.OperationType == OperationType.Upsert || request.OperationType == OperationType.Patch) { - return new DocumentServiceResponse( + response = new DocumentServiceResponse( new MemoryStream(MockRequestHelper.testItemResponsePayload), headers, System.Net.HttpStatusCode.OK ); } - - if (request.ResourceType == ResourceType.Document && + else if (request.ResourceType == ResourceType.Document && request.OperationType == OperationType.ReadFeed) { - return new DocumentServiceResponse( + response = new DocumentServiceResponse( new MemoryStream(MockRequestHelper.testItemFeedResponsePayload), headers, System.Net.HttpStatusCode.OK ); } + else + { + // If we reach here, it's an operation we are not explicitly handling + return null; + } - return null; + // Check if binary encoding is enabled and this is a supported point operation + if (ConfigurationManager.IsBinaryEncodingEnabled() && MockRequestHelper.IsPointOperationSupportedForBinaryEncoding(request)) + { + response = ConvertToBinaryIfNeeded(response); + } + + return response; } /// - /// For mocking a TransportClient response/ + /// For mocking a TransportClient response /// /// The instance. /// A instance. public static StoreResponse GetStoreResponse(DocumentServiceRequest request) { + StoreResponse response = null; + if (request.ResourceType == ResourceType.Document && - request.OperationType == OperationType.Query) + request.OperationType == OperationType.Query) { StoreResponseNameValueCollection queryHeaders = new StoreResponseNameValueCollection() { @@ -144,7 +166,7 @@ public static StoreResponse GetStoreResponse(DocumentServiceRequest request) CurrentReplicaSetSize = "1", CurrentWriteQuorum = "1", CurrentResourceQuotaUsage = "documentSize=0;documentsSize=1;documentsCount=1;collectionSize=1;", - GlobalCommittedLSN = "-1", + GlobalCommittedLSN = "-1", ItemCount = "1", LSN = "2540", LocalLSN = "2540", @@ -179,96 +201,94 @@ public static StoreResponse GetStoreResponse(DocumentServiceRequest request) { pagenumber = 0; } - - return new StoreResponse() - { - ResponseBody = new MemoryStream(MockRequestHelper.testItemFeedResponsePayload, 0, MockRequestHelper.testItemFeedResponsePayload.Length, writable: false, publiclyVisible: true), - Status = (int)System.Net.HttpStatusCode.OK, - Headers = queryHeaders, - }; } - return new StoreResponse() + response = new StoreResponse() { ResponseBody = new MemoryStream(MockRequestHelper.testItemFeedResponsePayload, 0, MockRequestHelper.testItemFeedResponsePayload.Length, writable: false, publiclyVisible: true), Status = (int)System.Net.HttpStatusCode.OK, Headers = queryHeaders, }; + + // Query isn't a single-point operation like Create/Read/Replace etc., so no binary encoding here + return response; } StoreResponseNameValueCollection headers = MockRequestHelper.GenerateTestHeaders(); - if (request.OperationType == OperationType.Read) { headers.Add(WFConstants.BackendHeaders.LSN, "1"); +#pragma warning disable IDE0045 // Convert to conditional expression if (request.ResourceAddress.EndsWith(MockedItemBenchmarkHelper.ExistingItemId)) { - return new StoreResponse() + response = new StoreResponse() { ResponseBody = new MemoryStream(MockRequestHelper.testItemResponsePayload, 0, MockRequestHelper.testItemResponsePayload.Length, writable: false, publiclyVisible: true), Status = (int)System.Net.HttpStatusCode.OK, Headers = headers, }; } - - return new StoreResponse() + else { - ResponseBody = new MemoryStream(MockRequestHelper.notFoundPayload, 0, MockRequestHelper.notFoundPayload.Length, writable: false, publiclyVisible: true), - Status = (int)System.Net.HttpStatusCode.NotFound, - Headers = headers, - }; + response = new StoreResponse() + { + ResponseBody = new MemoryStream(MockRequestHelper.notFoundPayload, 0, MockRequestHelper.notFoundPayload.Length, writable: false, publiclyVisible: true), + Status = (int)System.Net.HttpStatusCode.NotFound, + Headers = headers, + }; + } +#pragma warning restore IDE0045 // Convert to conditional expression } - - if (request.OperationType == OperationType.Delete) + else if (request.OperationType == OperationType.Delete) { +#pragma warning disable IDE0045 // Convert to conditional expression if (request.ResourceAddress.EndsWith(MockedItemBenchmarkHelper.ExistingItemId)) { - return new StoreResponse() + response = new StoreResponse() { ResponseBody = new MemoryStream(MockRequestHelper.testItemResponsePayload, 0, MockRequestHelper.testItemResponsePayload.Length, writable: false, publiclyVisible: true), Status = (int)System.Net.HttpStatusCode.OK, Headers = headers, }; } - - return new StoreResponse() + else { - ResponseBody = new MemoryStream(MockRequestHelper.notFoundPayload, 0, MockRequestHelper.notFoundPayload.Length, writable: false, publiclyVisible: true), - Status = (int)System.Net.HttpStatusCode.NotFound, - Headers = headers, - }; + response = new StoreResponse() + { + ResponseBody = new MemoryStream(MockRequestHelper.notFoundPayload, 0, MockRequestHelper.notFoundPayload.Length, writable: false, publiclyVisible: true), + Status = (int)System.Net.HttpStatusCode.NotFound, + Headers = headers, + }; + } +#pragma warning restore IDE0045 // Convert to conditional expression } - - if (request.OperationType == OperationType.Create + else if (request.OperationType == OperationType.Create || request.OperationType == OperationType.Replace || request.OperationType == OperationType.Upsert || request.OperationType == OperationType.Patch) { - return new StoreResponse() + response = new StoreResponse() { ResponseBody = new MemoryStream(MockRequestHelper.testItemResponsePayload, 0, MockRequestHelper.testItemResponsePayload.Length, writable: false, publiclyVisible: true), Status = (int)System.Net.HttpStatusCode.OK, Headers = headers, }; } - - if (request.ResourceType == ResourceType.Document && + else if (request.ResourceType == ResourceType.Document && request.OperationType == OperationType.ReadFeed) - { + { headers.ItemCount = "1"; - return new StoreResponse() + response = new StoreResponse() { ResponseBody = new MemoryStream(MockRequestHelper.testItemFeedResponsePayload, 0, MockRequestHelper.testItemFeedResponsePayload.Length, writable: false, publiclyVisible: true), Status = (int)System.Net.HttpStatusCode.OK, Headers = headers, }; } - - if (request.OperationType == OperationType.Batch) + else if (request.OperationType == OperationType.Batch) { MemoryStream responseContent = batchResponsePayloadWriter.GeneratePayload(); - - return new StoreResponse() + response = new StoreResponse() { ResponseBody = responseContent, Status = (int)System.Net.HttpStatusCode.OK, @@ -276,7 +296,12 @@ public static StoreResponse GetStoreResponse(DocumentServiceRequest request) }; } - return null; + if (response != null && ConfigurationManager.IsBinaryEncodingEnabled() && MockRequestHelper.IsPointOperationSupportedForBinaryEncoding(request)) + { + response = ConvertToBinaryIfNeeded(response); + } + + return response; } private static StoreResponseNameValueCollection GenerateTestHeaders() @@ -290,5 +315,80 @@ private static StoreResponseNameValueCollection GenerateTestHeaders() return headers; } + + // Converts the response payload into binary if needed + // This simulates binary serialization using the same approach as in MockedItemBenchmarkHelper. + private static DocumentServiceResponse ConvertToBinaryIfNeeded(DocumentServiceResponse response) + { + byte[] originalPayload = ReadAllBytes(response.ResponseBody); + byte[] binaryPayload = ReSerializeToBinary(originalPayload); + + // Replace stream with binary version + response.ResponseBody.Dispose(); + response.ResponseBody = new MemoryStream(binaryPayload, writable: false); + + // Add binary serialization header + response.Headers[HttpConstants.HttpHeaders.SupportedSerializationFormats] = BinarySerializationFormat; + + return response; + } + + private static StoreResponse ConvertToBinaryIfNeeded(StoreResponse response) + { + byte[] originalPayload = ReadAllBytes(response.ResponseBody); + byte[] binaryPayload = ReSerializeToBinary(originalPayload); + + // Replace stream with binary version + response.ResponseBody.Dispose(); + response.ResponseBody = new MemoryStream(binaryPayload, writable: false); + + // Add binary serialization header + response.Headers[HttpConstants.HttpHeaders.SupportedSerializationFormats] = BinarySerializationFormat; + + return response; + } + + private static byte[] ReSerializeToBinary(byte[] textPayload) + { + // Deserialize from JSON text + using (MemoryStream ms = new MemoryStream(textPayload)) + using (StreamReader sr = new StreamReader(ms)) + using (Newtonsoft.Json.JsonReader jr = new JsonTextReader(sr)) + { + Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); + // Assuming the payload matches the ToDoActivity schema + ToDoActivity deserialized = serializer.Deserialize(jr); + + using (CosmosDBToNewtonsoftWriter writer = new CosmosDBToNewtonsoftWriter(JsonSerializationFormat.Binary)) + { + serializer.Serialize(writer, deserialized); + return writer.GetResult().ToArray(); + } + } + } + + private static byte[] ReadAllBytes(Stream stream) + { + if (stream is MemoryStream ms && ms.Position == 0) + { + return ms.ToArray(); + } + + using (MemoryStream copy = new MemoryStream()) + { + stream.CopyTo(copy); + return copy.ToArray(); + } + } + + private static bool IsPointOperationSupportedForBinaryEncoding(DocumentServiceRequest request) + { + return request.ResourceType == ResourceType.Document + && (request.OperationType == OperationType.Create + || request.OperationType == OperationType.Replace + || request.OperationType == OperationType.Delete + || request.OperationType == OperationType.Read + || request.OperationType == OperationType.Upsert); + } } -} +} \ No newline at end of file