diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs index 499eab03d3..c0472328ad 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs @@ -157,6 +157,7 @@ public virtual bool TryAdd(ItemBatchOperation operation) foreach (ItemBatchOperation itemBatchOperation in batchResponse.Operations) { BatchOperationResult response = batchResponse[itemBatchOperation.OperationIndex]; + itemBatchOperation.Context.Diagnostics.AppendDiagnostics(batchResponse.Diagnostics); if (!response.IsSuccessStatusCode) { Documents.ShouldRetryResult shouldRetry = await itemBatchOperation.Context.ShouldRetryAsync(response, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchOperationResult.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchOperationResult.cs index fe2730ea92..1dda376966 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchOperationResult.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchOperationResult.cs @@ -85,6 +85,11 @@ public virtual bool IsSuccessStatusCode /// internal virtual SubStatusCodes SubStatusCode { get; set; } + /// + /// Gets the cosmos diagnostic information for the current request to Azure Cosmos DB service + /// + internal virtual CosmosDiagnostics Diagnostics { get; set; } + internal static Result ReadOperationResult(Memory input, out BatchOperationResult batchOperationResult) { RowBuffer row = new RowBuffer(input.Length); @@ -184,6 +189,7 @@ internal ResponseMessage ToResponseMessage() responseMessage.Headers.ETag = this.ETag; responseMessage.Headers.RetryAfter = this.RetryAfter; responseMessage.Content = this.ResourceStream; + responseMessage.Diagnostics = this.Diagnostics; return responseMessage; } } diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchResponse.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchResponse.cs index a031d75c0a..2a5e03e52e 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchResponse.cs @@ -36,9 +36,10 @@ internal BatchResponse( double requestCharge, TimeSpan? retryAfter, string activityId, + CosmosDiagnostics cosmosDiagnostics, ServerBatchRequest serverRequest, CosmosSerializer serializer) - : this(statusCode, subStatusCode, errorMessage, requestCharge, retryAfter, activityId, serverRequest.Operations, serializer) + : this(statusCode, subStatusCode, errorMessage, requestCharge, retryAfter, activityId, cosmosDiagnostics, serverRequest.Operations, serializer) { } @@ -61,6 +62,7 @@ internal BatchResponse( requestCharge: 0, retryAfter: null, activityId: Guid.Empty.ToString(), + cosmosDiagnostics: null, operations: operations, serializer: null) { @@ -80,6 +82,7 @@ private BatchResponse( double requestCharge, TimeSpan? retryAfter, string activityId, + CosmosDiagnostics cosmosDiagnostics, IReadOnlyList operations, CosmosSerializer serializer) { @@ -91,6 +94,7 @@ private BatchResponse( this.RequestCharge = requestCharge; this.RetryAfter = retryAfter; this.ActivityId = activityId; + this.Diagnostics = cosmosDiagnostics; } /// @@ -140,6 +144,11 @@ public virtual bool IsSuccessStatusCode /// public virtual int Count => this.results?.Count ?? 0; + /// + /// Gets the cosmos diagnostic information for the current request to Azure Cosmos DB service + /// + public virtual CosmosDiagnostics Diagnostics { get; } + internal virtual SubStatusCodes SubStatusCode { get; } internal virtual CosmosSerializer Serializer { get; } @@ -236,6 +245,7 @@ internal static async Task FromResponseMessageAsync( responseMessage.Headers.RequestCharge, responseMessage.Headers.RetryAfter, responseMessage.Headers.ActivityId, + responseMessage.Diagnostics, serverRequest, serializer); } @@ -249,6 +259,7 @@ internal static async Task FromResponseMessageAsync( responseMessage.Headers.RequestCharge, responseMessage.Headers.RetryAfter, responseMessage.Headers.ActivityId, + responseMessage.Diagnostics, serverRequest, serializer); } @@ -266,6 +277,7 @@ internal static async Task FromResponseMessageAsync( responseMessage.Headers.RequestCharge, responseMessage.Headers.RetryAfter, responseMessage.Headers.ActivityId, + responseMessage.Diagnostics, serverRequest, serializer); } @@ -338,6 +350,7 @@ record => responseMessage.Headers.RequestCharge, responseMessage.Headers.RetryAfter, responseMessage.Headers.ActivityId, + responseMessage.Diagnostics, serverRequest, serializer); diff --git a/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs b/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs index 833729d2a6..18ca705c71 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs @@ -21,6 +21,8 @@ internal class ItemBatchOperationContext : IDisposable public Task OperationTask => this.taskCompletionSource.Task; + public ItemBatchOperationStatistics Diagnostics { get; } = new ItemBatchOperationStatistics(); + private readonly IDocumentClientRetryPolicy retryPolicy; private TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); @@ -56,6 +58,8 @@ public void Complete( { if (this.AssertBatcher(completer)) { + this.Diagnostics.Complete(); + result.Diagnostics = this.Diagnostics; this.taskCompletionSource.SetResult(result); } diff --git a/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs b/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs index c2ff0fc1ab..5feb05c498 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/PartitionKeyRangeBatchResponse.cs @@ -17,6 +17,7 @@ internal class PartitionKeyRangeBatchResponse : BatchResponse { // Results sorted in the order operations had been added. private readonly BatchOperationResult[] resultsByOperationIndex; + private readonly BatchResponse serverResponse; private bool isDisposed; /// @@ -49,11 +50,10 @@ internal PartitionKeyRangeBatchResponse( { this.StatusCode = serverResponse.StatusCode; - this.ServerResponse = serverResponse; + this.serverResponse = serverResponse; this.resultsByOperationIndex = new BatchOperationResult[originalOperationsCount]; StringBuilder errorMessageBuilder = new StringBuilder(); - List activityIds = new List(); List itemBatchOperations = new List(); // We expect number of results == number of operations here for (int index = 0; index < serverResponse.Operations.Count; index++) @@ -74,7 +74,6 @@ internal PartitionKeyRangeBatchResponse( errorMessageBuilder.AppendFormat("{0}; ", serverResponse.ErrorMessage); } - this.ActivityId = serverResponse.ActivityId; this.ErrorMessage = errorMessageBuilder.Length > 2 ? errorMessageBuilder.ToString(0, errorMessageBuilder.Length - 2) : null; this.Operations = itemBatchOperations; this.Serializer = serializer; @@ -83,12 +82,12 @@ internal PartitionKeyRangeBatchResponse( /// /// Gets the ActivityId that identifies the server request made to execute the batch request. /// - public override string ActivityId { get; } + public override string ActivityId => this.serverResponse.ActivityId; - internal override CosmosSerializer Serializer { get; } + /// + public override CosmosDiagnostics Diagnostics => this.serverResponse.Diagnostics; - // for unit testing only - internal BatchResponse ServerResponse { get; private set; } + internal override CosmosSerializer Serializer { get; } /// /// Gets the number of operation results. @@ -148,7 +147,7 @@ protected override void Dispose(bool disposing) if (disposing && !this.isDisposed) { this.isDisposed = true; - this.ServerResponse?.Dispose(); + this.serverResponse?.Dispose(); } base.Dispose(disposing); diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 4fbb4d29c5..d426030c80 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos using System.IO; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Handlers; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Scripts; using Microsoft.Azure.Documents; @@ -290,6 +289,11 @@ internal virtual Task GetRoutingMapAsync(CancellationToken internal virtual BatchAsyncContainerExecutor InitializeBatchExecutorForContainer() { + if (!this.ClientContext.ClientOptions.AllowBulkExecution) + { + return null; + } + return new BatchAsyncContainerExecutor( this, this.ClientContext, diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ItemBatchOperationStatistics.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ItemBatchOperationStatistics.cs new file mode 100644 index 0000000000..f747ccad70 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ItemBatchOperationStatistics.cs @@ -0,0 +1,51 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// A batch operation might extend multiple requests due to retries. + /// + internal class ItemBatchOperationStatistics : CosmosDiagnostics + { + private readonly DateTime created = DateTime.UtcNow; + private readonly List cosmosDiagnostics = new List(); + private DateTime completed; + + public void AppendDiagnostics(CosmosDiagnostics diagnostics) + { + this.cosmosDiagnostics.Add(diagnostics); + } + + public void Complete() + { + this.completed = DateTime.UtcNow; + } + + public override string ToString() + { + if (this.cosmosDiagnostics.Count == 0) + { + return string.Empty; + } + + StringBuilder statistics = new StringBuilder($"Bulk operation started at {this.created}. "); + if (this.completed != null) + { + statistics.Append($"Completed at {this.completed}. "); + } + + foreach (CosmosDiagnostics pointOperationStatistic in this.cosmosDiagnostics) + { + statistics.AppendLine(pointOperationStatistic.ToString()); + } + + return statistics.ToString(); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/CosmosItemBulkTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/CosmosItemBulkTests.cs index e6b4f46d70..3ef76f423d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/CosmosItemBulkTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Batch/CosmosItemBulkTests.cs @@ -54,7 +54,7 @@ public async Task CreateItemStream_WithBulk() Task task = tasks[i]; ResponseMessage result = await task; Assert.AreEqual(HttpStatusCode.Created, result.StatusCode); - + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); MyDocument document = cosmosDefaultJsonSerializer.FromStream(result.Content); Assert.AreEqual(i.ToString(), document.id); } @@ -75,6 +75,7 @@ public async Task CreateItemAsync_WithBulk() { Task> task = tasks[i]; ItemResponse result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.Created, result.StatusCode); } } @@ -95,7 +96,7 @@ public async Task UpsertItemStream_WithBulk() Task task = tasks[i]; ResponseMessage result = await task; Assert.AreEqual(HttpStatusCode.Created, result.StatusCode); - + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); MyDocument document = cosmosDefaultJsonSerializer.FromStream(result.Content); Assert.AreEqual(i.ToString(), document.id); } @@ -116,6 +117,7 @@ public async Task UpsertItem_WithBulk() { Task> task = tasks[i]; ItemResponse result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.Created, result.StatusCode); } } @@ -147,6 +149,7 @@ public async Task DeleteItemStream_WithBulk() { Task task = deleteTasks[i]; ResponseMessage result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode); } } @@ -178,6 +181,7 @@ public async Task DeleteItem_WithBulk() { Task> task = deleteTasks[i]; ItemResponse result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.NoContent, result.StatusCode); } } @@ -198,7 +202,7 @@ public async Task ReadItemStream_WithBulk() await Task.WhenAll(tasks); List> readTasks = new List>(); - // Delete the items + // Read the items foreach (MyDocument createdDocument in createdDocuments) { readTasks.Add(ExecuteReadStreamAsync(this.container, createdDocument)); @@ -209,6 +213,7 @@ public async Task ReadItemStream_WithBulk() { Task task = readTasks[i]; ResponseMessage result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); } } @@ -229,7 +234,7 @@ public async Task ReadItem_WithBulk() await Task.WhenAll(tasks); List>> readTasks = new List>>(); - // Delete the items + // Read the items foreach (MyDocument createdDocument in createdDocuments) { readTasks.Add(ExecuteReadAsync(this.container, createdDocument)); @@ -240,6 +245,7 @@ public async Task ReadItem_WithBulk() { Task> task = readTasks[i]; ItemResponse result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); } } @@ -271,6 +277,7 @@ public async Task ReplaceItemStream_WithBulk() { Task task = replaceTasks[i]; ResponseMessage result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); } } @@ -302,6 +309,7 @@ public async Task ReplaceItem_WithBulk() { Task> task = replaceTasks[i]; ItemResponse result = await task; + Assert.IsFalse(string.IsNullOrEmpty(result.Diagnostics.ToString())); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs index aea5279738..b1c1b57278 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncBatcherTests.cs @@ -59,7 +59,7 @@ private BatchAsyncBatcherExecuteDelegate Executor cancellationToken: cancellationToken); BatchResponse batchresponse = await BatchResponse.PopulateFromContentAsync( - new ResponseMessage(HttpStatusCode.OK) { Content = responseContent }, + new ResponseMessage(HttpStatusCode.OK) { Content = responseContent, Diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()) }, batchRequest, new CosmosJsonDotNetSerializer()); @@ -94,7 +94,7 @@ private BatchAsyncBatcherExecuteDelegate ExecutorWithSplit serializer: new CosmosJsonDotNetSerializer(), cancellationToken: cancellationToken); - ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.Gone) { Content = responseContent }; + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.Gone) { Content = responseContent, Diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()) }; responseMessage.Headers.SubStatusCode = SubStatusCodes.PartitionKeyRangeGone; BatchResponse batchresponse = await BatchResponse.PopulateFromContentAsync( @@ -238,13 +238,13 @@ public async Task ExceptionsFailOperationsAsync() public async Task DispatchProcessInOrderAsync() { BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(10, 1000, new CosmosJsonDotNetSerializer(), this.Executor, this.Retrier); - List contexts = new List(10); + List operations = new List(10); for (int i = 0; i < 10; i++) { ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, i, i.ToString()); ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty); operation.AttachContext(context); - contexts.Add(context); + operations.Add(operation); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation)); } @@ -252,10 +252,14 @@ public async Task DispatchProcessInOrderAsync() for (int i = 0; i < 10; i++) { - ItemBatchOperationContext context = contexts[i]; - Assert.AreEqual(TaskStatus.RanToCompletion, context.OperationTask.Status); - BatchOperationResult result = await context.OperationTask; + ItemBatchOperation operation = operations[i]; + Assert.AreEqual(TaskStatus.RanToCompletion, operation.Context.OperationTask.Status); + BatchOperationResult result = await operation.Context.OperationTask; Assert.AreEqual(i.ToString(), result.ETag); + + Assert.IsNotNull(operation.Context.Diagnostics); + Assert.AreEqual(operation.Context.Diagnostics.ToString(), result.Diagnostics.ToString()); + Assert.IsFalse(string.IsNullOrEmpty(operation.Context.Diagnostics.ToString())); } } @@ -361,6 +365,10 @@ public async Task RetrierGetsCalledOnSplit() retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny()), Times.Once); retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny()), Times.Once); retryDelegate.Verify(a => a(It.IsAny(), It.IsAny()), Times.Exactly(2)); + Assert.IsNotNull(operation1.Context.Diagnostics); + Assert.IsNotNull(operation2.Context.Diagnostics); + Assert.IsTrue(!string.IsNullOrEmpty(operation1.Context.Diagnostics.ToString())); + Assert.IsTrue(!string.IsNullOrEmpty(operation2.Context.Diagnostics.ToString())); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs index 8bb5ae30c0..cfb658af6e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncContainerExecutorTests.cs @@ -73,6 +73,18 @@ public async Task RetryOnSplit() It.IsAny>(), It.IsAny()), Times.Exactly(2)); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + Assert.IsNotNull(result.Diagnostics); + + int diagnosticsLines = 0; + string diagnosticsString = result.Diagnostics.ToString(); + int index = diagnosticsString.IndexOf(Environment.NewLine); + while(index > -1) + { + diagnosticsLines++; + index = diagnosticsString.IndexOf(Environment.NewLine, index + 1); + } + + Assert.IsTrue(diagnosticsLines > 2); } [TestMethod] @@ -127,6 +139,18 @@ public async Task RetryOn429() It.IsAny>(), It.IsAny()), Times.Exactly(2)); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode); + Assert.IsNotNull(result.Diagnostics); + + int diagnosticsLines = 0; + string diagnosticsString = result.Diagnostics.ToString(); + int index = diagnosticsString.IndexOf(Environment.NewLine); + while (index > -1) + { + diagnosticsLines++; + index = diagnosticsString.IndexOf(Environment.NewLine, index + 1); + } + + Assert.IsTrue(diagnosticsLines > 2); } [TestMethod] @@ -205,7 +229,7 @@ private async Task GenerateSplitResponseAsync(ItemBatchOperatio serializer: new CosmosJsonDotNetSerializer(), cancellationToken: CancellationToken.None); - ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.Gone) { Content = responseContent }; + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.Gone) { Content = responseContent, Diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()) }; responseMessage.Headers.SubStatusCode = SubStatusCodes.PartitionKeyRangeGone; return responseMessage; } @@ -232,7 +256,7 @@ private async Task Generate429ResponseAsync(ItemBatchOperation serializer: new CosmosJsonDotNetSerializer(), cancellationToken: CancellationToken.None); - ResponseMessage responseMessage = new ResponseMessage((HttpStatusCode)StatusCodes.TooManyRequests) { Content = responseContent }; + ResponseMessage responseMessage = new ResponseMessage((HttpStatusCode)StatusCodes.TooManyRequests) { Content = responseContent, Diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()) }; return responseMessage; } @@ -258,7 +282,7 @@ private async Task GenerateOkResponseAsync(ItemBatchOperation i serializer: new CosmosJsonDotNetSerializer(), cancellationToken: CancellationToken.None); - ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK) { Content = responseContent }; + ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.OK) { Content = responseContent, Diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()) }; return responseMessage; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/ItemBatchOperationStatisticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/ItemBatchOperationStatisticsTests.cs new file mode 100644 index 0000000000..1291218c4f --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/ItemBatchOperationStatisticsTests.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tests +{ + using System; + using System.Net; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ItemBatchOperationStatisticsTests + { + [TestMethod] + public void ToString_WhenEmptyReturnEmptyString() + { + ItemBatchOperationStatistics itemBatchOperationStatistics = new ItemBatchOperationStatistics(); + Assert.IsTrue(string.IsNullOrEmpty(itemBatchOperationStatistics.ToString())); + } + + [TestMethod] + public void ToString_ReturnItemsToString() + { + ItemBatchOperationStatistics itemBatchOperationStatistics = new ItemBatchOperationStatistics(); + + CosmosClientSideRequestStatistics cosmosClientSideRequestStatistics1 = new CosmosClientSideRequestStatistics(); + cosmosClientSideRequestStatistics1.ContactedReplicas.Add(new Uri("https://one.com")); + PointOperationStatistics pointOperation1 = new PointOperationStatistics(cosmosClientSideRequestStatistics1); + + CosmosClientSideRequestStatistics cosmosClientSideRequestStatistics2 = new CosmosClientSideRequestStatistics(); + cosmosClientSideRequestStatistics2.ContactedReplicas.Add(new Uri("https://two.com")); + PointOperationStatistics pointOperation2 = new PointOperationStatistics(cosmosClientSideRequestStatistics2); + + itemBatchOperationStatistics.AppendDiagnostics(pointOperation1); + itemBatchOperationStatistics.AppendDiagnostics(pointOperation2); + + string toString = itemBatchOperationStatistics.ToString(); + + Assert.IsTrue(toString.Contains(pointOperation1.ToString())); + Assert.IsTrue(toString.Contains(pointOperation2.ToString())); + } + + [TestMethod] + public void Complete_AddsCompleteTime() + { + ItemBatchOperationStatistics itemBatchOperationStatistics = new ItemBatchOperationStatistics(); + + CosmosClientSideRequestStatistics cosmosClientSideRequestStatistics1 = new CosmosClientSideRequestStatistics(); + cosmosClientSideRequestStatistics1.ContactedReplicas.Add(new Uri("https://one.com")); + PointOperationStatistics pointOperation1 = new PointOperationStatistics(cosmosClientSideRequestStatistics1); + + CosmosClientSideRequestStatistics cosmosClientSideRequestStatistics2 = new CosmosClientSideRequestStatistics(); + cosmosClientSideRequestStatistics2.ContactedReplicas.Add(new Uri("https://two.com")); + PointOperationStatistics pointOperation2 = new PointOperationStatistics(cosmosClientSideRequestStatistics2); + + itemBatchOperationStatistics.AppendDiagnostics(pointOperation1); + itemBatchOperationStatistics.AppendDiagnostics(pointOperation2); + itemBatchOperationStatistics.Complete(); + + Assert.IsTrue(itemBatchOperationStatistics.ToString().Contains("Completed at")); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyBatchResponseTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyBatchResponseTests.cs index 3deb8b54e4..f544256fe9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyBatchResponseTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyBatchResponseTests.cs @@ -61,5 +61,43 @@ public async Task StatusCodesAreSetThroughResponseAsync() PartitionKeyRangeBatchResponse response = new PartitionKeyRangeBatchResponse(arrayOperations.Length, batchresponse, new CosmosJsonDotNetSerializer()); Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); } + + [TestMethod] + public async Task DiagnosticsAreSetThroughResponseAsync() + { + List results = new List(); + ItemBatchOperation[] arrayOperations = new ItemBatchOperation[1]; + + ItemBatchOperation operation = new ItemBatchOperation(OperationType.AddComputeGatewayRequestCharges, 0, "0"); + + results.Add( + new BatchOperationResult(HttpStatusCode.OK) + { + ResourceStream = new MemoryStream(new byte[] { 0x41, 0x42 }, index: 0, count: 2, writable: false, publiclyVisible: true), + ETag = operation.Id + }); + + arrayOperations[0] = operation; + + MemoryStream responseContent = await new BatchResponsePayloadWriter(results).GeneratePayloadAsync(); + + SinglePartitionKeyServerBatchRequest batchRequest = await SinglePartitionKeyServerBatchRequest.CreateAsync( + partitionKey: null, + operations: new ArraySegment(arrayOperations), + maxBodyLength: 100, + maxOperationCount: 1, + serializer: new CosmosJsonDotNetSerializer(), + cancellationToken: default(CancellationToken)); + + CosmosDiagnostics diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()); + + BatchResponse batchresponse = await BatchResponse.PopulateFromContentAsync( + new ResponseMessage(HttpStatusCode.OK) { Content = responseContent, Diagnostics = diagnostics }, + batchRequest, + new CosmosJsonDotNetSerializer()); + + PartitionKeyRangeBatchResponse response = new PartitionKeyRangeBatchResponse(arrayOperations.Length, batchresponse, new CosmosJsonDotNetSerializer()); + Assert.AreEqual(diagnostics, response.Diagnostics); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyRangeBatchExecutionResultTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyRangeBatchExecutionResultTests.cs index da594dde21..5c933f51a9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyRangeBatchExecutionResultTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/PartitionKeyRangeBatchExecutionResultTests.cs @@ -75,7 +75,8 @@ public void ToResponseMessage_MapsProperties() ResourceStream = new MemoryStream(new byte[] { 0x41, 0x42 }, index: 0, count: 2, writable: false, publiclyVisible: true), ETag = "1234", SubStatusCode = SubStatusCodes.CompletingSplit, - RetryAfter = TimeSpan.FromSeconds(10) + RetryAfter = TimeSpan.FromSeconds(10), + Diagnostics = new PointOperationStatistics(new CosmosClientSideRequestStatistics()) }; ResponseMessage response = result.ToResponseMessage(); @@ -84,6 +85,7 @@ public void ToResponseMessage_MapsProperties() Assert.AreEqual(result.SubStatusCode, response.Headers.SubStatusCode); Assert.AreEqual(result.RetryAfter, response.Headers.RetryAfter); Assert.AreEqual(result.StatusCode, response.StatusCode); + Assert.AreEqual(result.Diagnostics, response.Diagnostics); } private async Task ConstainsSplitIsTrueInternal(HttpStatusCode statusCode, SubStatusCodes subStatusCode) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientResourceUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientResourceUnitTests.cs index 97ba44a1dd..f2198540f8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientResourceUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientResourceUnitTests.cs @@ -114,5 +114,47 @@ public void ValidateSetItemRequestOptions() Assert.IsTrue(httpRequest.Headers.TryGetValue(HttpConstants.HttpHeaders.PostTriggerInclude, out string postTriggerHeader)); } + [TestMethod] + public void InitializeBatchExecutorForContainer_Null_WhenAllowBulk_False() + { + string databaseId = "db1234"; + string crId = "cr42"; + + CosmosClientContext context = new ClientContextCore( + client: null, + clientOptions: new CosmosClientOptions(), + userJsonSerializer: null, + defaultJsonSerializer: null, + sqlQuerySpecSerializer: null, + cosmosResponseFactory: null, + requestHandler: null, + documentClient: null); + + DatabaseCore db = new DatabaseCore(context, databaseId); + ContainerCore container = new ContainerCore(context, db, crId); + Assert.IsNull(container.BatchExecutor); + } + + [TestMethod] + public void InitializeBatchExecutorForContainer_NotNull_WhenAllowBulk_True() + { + string databaseId = "db1234"; + string crId = "cr42"; + + CosmosClientContext context = new ClientContextCore( + client: null, + clientOptions: new CosmosClientOptions() { AllowBulkExecution = true }, + userJsonSerializer: null, + defaultJsonSerializer: null, + sqlQuerySpecSerializer: null, + cosmosResponseFactory: null, + requestHandler: null, + documentClient: null); + + DatabaseCore db = new DatabaseCore(context, databaseId); + ContainerCore container = new ContainerCore(context, db, crId); + Assert.IsNotNull(container.BatchExecutor); + } + } }