diff --git a/Directory.Build.props b/Directory.Build.props index d5fab5b1f6..411d1caa3e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,11 @@ - 3.18.0 + 3.19.0 3.19.0 - preview + preview1 3.19.1 - 1.0.0-previewV13 + 1.0.0-previewV15 1.1.0-preview3 $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) $(DefineConstants);PREVIEW diff --git a/Microsoft.Azure.Cosmos.Encryption/changelog.md b/Microsoft.Azure.Cosmos.Encryption/changelog.md index 6ddfdd45dc..ca412e6650 100644 --- a/Microsoft.Azure.Cosmos.Encryption/changelog.md +++ b/Microsoft.Azure.Cosmos.Encryption/changelog.md @@ -3,6 +3,25 @@ Preview features are treated as a separate branch and will not be included in th The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [1.0.0-previewV15](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption/1.0.0-previewV15) - 2021-05-18 + +#### Added +- [#2475](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2475) Adds integration with latest CosmosDb Preview Package - 3.19.0-preview1 and check for Client Encryption Policy format version. +- [#2449](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2449) Adds validation that partition key paths are not encrypted. +- [#2452](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2452) Adds the implementation for new ChangeFeed APIs. +- [#2448](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2448) Adds client encryption support for patch. +- [#2453](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2453) Removes Plaintext encryption type support. + +#### Fixes +- [#2445](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2445) Fixes query support on encrypted parameters and fixes samples. +- [#2403](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2403) Fixes issue with clients using incorrect/stale Encryption Policy or Encryption Keys from the cache. + +### [1.0.0-previewV14](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption/1.0.0-previewV14) - 2021-04-28 + +#### Added +- [#2433](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2433) Adds integration with latest CosmosDb Preview Package - 3.19.0-preview. +- [#2372](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2372) Adds decryption support for ChangeFeed Processor. + ### [1.0.0-previewV13](https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Encryption/1.0.0-previewV13) - 2021-03-26 #### Added diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Constants.cs b/Microsoft.Azure.Cosmos.Encryption/src/Constants.cs index ebecfac521..8b8e2c6e01 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Constants.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/Constants.cs @@ -6,7 +6,9 @@ namespace Microsoft.Azure.Cosmos.Encryption { internal static class Constants { - public const int CachedEncryptionSettingsDefaultTTLInMinutes = 60; public const string DocumentsResourcePropertyName = "Documents"; + public const string SubStatusHeader = "x-ms-substatus"; + public const string IncorrectContainerRidSubStatus = "1024"; + public const int SupportedClientEncryptionPolicyFormatVersion = 1; } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption/src/CosmosEncryptionType.cs b/Microsoft.Azure.Cosmos.Encryption/src/CosmosEncryptionType.cs index cf8a63752d..111b54e3bf 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/CosmosEncryptionType.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/CosmosEncryptionType.cs @@ -9,11 +9,6 @@ namespace Microsoft.Azure.Cosmos.Encryption /// internal static class CosmosEncryptionType { - /// - /// Plaintext, unencrypted data. - /// - public const string Plaintext = "Plaintext"; - /// /// Deterministic encryption always generates the same encrypted value for any given plain text value. /// diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Custom/AeadAes256CbcHmac256Algorithm.cs b/Microsoft.Azure.Cosmos.Encryption/src/Custom/AeadAes256CbcHmac256Algorithm.cs index 596e272d9a..df416c3efd 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Custom/AeadAes256CbcHmac256Algorithm.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/Custom/AeadAes256CbcHmac256Algorithm.cs @@ -198,10 +198,7 @@ protected byte[] EncryptData(byte[] plainText, bool hasAuthenticationTag) } catch (Exception) { - if (aesAlg != null) - { - aesAlg.Dispose(); - } + aesAlg?.Dispose(); throw; } @@ -352,10 +349,7 @@ private byte[] DecryptData(byte[] iv, byte[] cipherText, int offset, int count) } catch (Exception) { - if (aesAlg != null) - { - aesAlg.Dispose(); - } + aesAlg?.Dispose(); throw; } diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionContainer.cs index bf821a1f90..616bf1bf42 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionContainer.cs @@ -774,50 +774,6 @@ public override FeedIterator GetItemQueryStreamIterator( this.CosmosSerializer); } - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangesHandler onChangesDelegate) - { - CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(null); - using (diagnosticsContext.CreateScope("GetChangeFeedProcessorBuilder")) - { - return this.container.GetChangeFeedProcessorBuilder( - processorName, - async (IReadOnlyCollection documents, CancellationToken cancellationToken) => - { - List decryptItems = new List(documents.Count); - if (typeof(T) == typeof(DecryptableItem)) - { - foreach (JToken value in documents) - { - DecryptableItemCore item = new DecryptableItemCore( - value, - this.Encryptor, - this.CosmosSerializer); - - decryptItems.Add((T)(object)item); - } - } - else - { - foreach (JObject document in documents) - { - (JObject decryptedDocument, DecryptionContext _) = await EncryptionProcessor.DecryptAsync( - document, - this.Encryptor, - diagnosticsContext, - cancellationToken); - - decryptItems.Add(decryptedDocument.ToObject()); - } - } - - // Call the original passed in delegate - await onChangesDelegate(decryptItems, cancellationToken); - }); - } - } - public override Task ReplaceThroughputAsync( ThroughputProperties throughputProperties, RequestOptions requestOptions = null, @@ -926,5 +882,162 @@ public override Task PatchItemStreamAsync( { throw new NotImplementedException(); } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangesHandler onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilder( + processorName, + async ( + IReadOnlyCollection documents, + CancellationToken cancellationToken) => + { + List decryptItems = await this.DecryptChangeFeedDocumentsAsync( + documents, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(decryptItems, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedHandler onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilder( + processorName, + async ( + ChangeFeedProcessorContext context, + IReadOnlyCollection documents, + CancellationToken cancellationToken) => + { + List decryptItems = await this.DecryptChangeFeedDocumentsAsync( + documents, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptItems, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + processorName, + async ( + ChangeFeedProcessorContext context, + IReadOnlyCollection documents, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => + { + List decryptItems = await this.DecryptChangeFeedDocumentsAsync( + documents, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptItems, tryCheckpointAsync, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedStreamHandler onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilder( + processorName, + async ( + ChangeFeedProcessorContext context, + Stream changes, + CancellationToken cancellationToken) => + { + Stream decryptedChanges = await EncryptionProcessor.DeserializeAndDecryptResponseAsync( + changes, + this.Encryptor, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptedChanges, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + processorName, + async ( + ChangeFeedProcessorContext context, + Stream changes, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => + { + Stream decryptedChanges = await EncryptionProcessor.DeserializeAndDecryptResponseAsync( + changes, + this.Encryptor, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptedChanges, tryCheckpointAsync, cancellationToken); + }); + } + + public override Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public override Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + private async Task> DecryptChangeFeedDocumentsAsync( + IReadOnlyCollection documents, + CancellationToken cancellationToken) + { + List decryptItems = new List(documents.Count); + if (typeof(T) == typeof(DecryptableItem)) + { + foreach (JToken value in documents) + { + DecryptableItemCore item = new DecryptableItemCore( + value, + this.Encryptor, + this.CosmosSerializer); + + decryptItems.Add((T)(object)item); + } + } + else + { + foreach (JObject document in documents) + { + CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(null); + using (diagnosticsContext.CreateScope("DecryptChangeFeedDocumentsAsync<")) + { + (JObject decryptedDocument, DecryptionContext _) = await EncryptionProcessor.DecryptAsync( + document, + this.Encryptor, + diagnosticsContext, + cancellationToken); + + decryptItems.Add(decryptedDocument.ToObject()); + } + } + } + + return decryptItems; + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionFeedIterator.cs b/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionFeedIterator.cs index 8fecc33453..e9bb0fa604 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionFeedIterator.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionFeedIterator.cs @@ -38,9 +38,9 @@ public override async Task ReadNextAsync(CancellationToken canc if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null) { - Stream decryptedContent = await this.DeserializeAndDecryptResponseAsync( + Stream decryptedContent = await EncryptionProcessor.DeserializeAndDecryptResponseAsync( responseMessage.Content, - diagnosticsContext, + this.encryptor, cancellationToken); return new DecryptedResponseMessage(responseMessage, decryptedContent); @@ -94,51 +94,5 @@ private List ConvertResponseToDecryptableItems( return decryptableItems; } - - private async Task DeserializeAndDecryptResponseAsync( - Stream content, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream(content); - JArray result = new JArray(); - - if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) - { - throw new InvalidOperationException("Feed Response body contract was violated. Feed response did not have an array of Documents"); - } - - foreach (JToken value in documents) - { - if (!(value is JObject document)) - { - result.Add(value); - continue; - } - - (JObject decryptedDocument, DecryptionContext _) = await EncryptionProcessor.DecryptAsync( - document, - this.encryptor, - diagnosticsContext, - cancellationToken); - - result.Add(decryptedDocument); - } - - JObject decryptedResponse = new JObject(); - foreach (JProperty property in contentJObj.Properties()) - { - if (property.Name.Equals(Constants.DocumentsResourcePropertyName)) - { - decryptedResponse.Add(property.Name, (JToken)result); - } - else - { - decryptedResponse.Add(property.Name, property.Value); - } - } - - return EncryptionProcessor.BaseSerializer.ToStream(decryptedResponse); - } } } diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionProcessor.cs index 236246fa28..70b316917f 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/Custom/EncryptionProcessor.cs @@ -478,7 +478,7 @@ private static JObject RetrieveEncryptionProperties( { JProperty encryptionPropertiesJProp = item.Property(Constants.EncryptedInfo); JObject encryptionPropertiesJObj = null; - if (encryptionPropertiesJProp != null && encryptionPropertiesJProp.Value != null && encryptionPropertiesJProp.Value.Type == JTokenType.Object) + if (encryptionPropertiesJProp?.Value != null && encryptionPropertiesJProp.Value.Type == JTokenType.Object) { encryptionPropertiesJObj = (JObject)encryptionPropertiesJProp.Value; } @@ -565,5 +565,55 @@ private enum TypeMarker : byte Array = 6, Object = 7, } + + internal static async Task DeserializeAndDecryptResponseAsync( + Stream content, + Encryptor encryptor, + CancellationToken cancellationToken) + { + JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream(content); + JArray result = new JArray(); + + if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) + { + throw new InvalidOperationException("Feed Response body contract was violated. Feed response did not have an array of Documents"); + } + + foreach (JToken value in documents) + { + if (!(value is JObject document)) + { + result.Add(value); + continue; + } + + CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(null); + using (diagnosticsContext.CreateScope("EncryptionProcessor.DeserializeAndDecryptResponseAsync")) + { + (JObject decryptedDocument, DecryptionContext _) = await EncryptionProcessor.DecryptAsync( + document, + encryptor, + diagnosticsContext, + cancellationToken); + + result.Add(decryptedDocument); + } + } + + JObject decryptedResponse = new JObject(); + foreach (JProperty property in contentJObj.Properties()) + { + if (property.Name.Equals(Constants.DocumentsResourcePropertyName)) + { + decryptedResponse.Add(property.Name, (JToken)result); + } + else + { + decryptedResponse.Add(property.Name, property.Value); + } + } + + return EncryptionProcessor.BaseSerializer.ToStream(decryptedResponse); + } } } diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs index e7eb8ceafd..fff1fb0a8b 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainer.cs @@ -8,27 +8,25 @@ namespace Microsoft.Azure.Cosmos.Encryption using System.Collections.Generic; using System.IO; using System.Linq; + using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; + using Microsoft.Data.Encryption.Cryptography; using Newtonsoft.Json.Linq; internal sealed class EncryptionContainer : Container { - public Container Container { get; private set; } + private readonly Container container; + + private readonly AsyncCache encryptionSettingsByContainerName; public CosmosSerializer CosmosSerializer { get; } public CosmosResponseFactory ResponseFactory { get; } - public EncryptionProcessor EncryptionProcessor { get; } - public EncryptionCosmosClient EncryptionCosmosClient { get; } - private bool isEncryptionContainerCacheInitDone; - - private static readonly SemaphoreSlim CacheInitSema = new SemaphoreSlim(1, 1); - /// /// All the operations / requests for exercising client-side encryption functionality need to be made using this EncryptionContainer instance. /// @@ -38,78 +36,20 @@ public EncryptionContainer( Container container, EncryptionCosmosClient encryptionCosmosClient) { - this.Container = container ?? throw new ArgumentNullException(nameof(container)); + this.container = container ?? throw new ArgumentNullException(nameof(container)); this.EncryptionCosmosClient = encryptionCosmosClient ?? throw new ArgumentNullException(nameof(container)); - - this.EncryptionProcessor = new EncryptionProcessor( - container, - this.EncryptionCosmosClient); - this.ResponseFactory = this.Database.Client.ResponseFactory; this.CosmosSerializer = this.Database.Client.ClientOptions.Serializer; - this.isEncryptionContainerCacheInitDone = false; + this.encryptionSettingsByContainerName = new AsyncCache(); } - public override string Id => this.Container.Id; - - public override Conflicts Conflicts => this.Container.Conflicts; - - public override Scripts.Scripts Scripts => this.Container.Scripts; - - public override Database Database => this.Container.Database; - - internal async Task InitEncryptionContainerCacheIfNotInitAsync(CancellationToken cancellationToken) - { - if (this.isEncryptionContainerCacheInitDone) - { - return; - } - - if (await CacheInitSema.WaitAsync(-1)) - { - if (!this.isEncryptionContainerCacheInitDone) - { - try - { - await this.InitContainerCacheAsync(cancellationToken); - await this.EncryptionProcessor.InitEncryptionSettingsIfNotInitializedAsync(); - this.isEncryptionContainerCacheInitDone = true; - } - finally - { - CacheInitSema.Release(1); - } - } - else - { - CacheInitSema.Release(1); - } - } - } + public override string Id => this.container.Id; - internal async Task InitContainerCacheAsync( - CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + public override Conflicts Conflicts => this.container.Conflicts; - EncryptionCosmosClient encryptionCosmosClient = this.EncryptionCosmosClient; - ClientEncryptionPolicy clientEncryptionPolicy = await encryptionCosmosClient.GetClientEncryptionPolicyAsync( - container: this, - cancellationToken: cancellationToken, - shouldForceRefresh: false); + public override Scripts.Scripts Scripts => this.container.Scripts; - if (clientEncryptionPolicy != null) - { - foreach (string clientEncryptionKeyId in clientEncryptionPolicy.IncludedPaths.Select(p => p.ClientEncryptionKeyId).Distinct()) - { - await this.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( - clientEncryptionKeyId: clientEncryptionKeyId, - container: this, - cancellationToken: cancellationToken, - shouldForceRefresh: false); - } - } - } + public override Database Database => this.container.Database; public override async Task> CreateItemAsync( T item, @@ -169,39 +109,13 @@ public override async Task CreateItemStreamAsync( } } - private async Task CreateItemHelperAsync( - Stream streamPayload, - PartitionKey partitionKey, - ItemRequestOptions requestOptions, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - streamPayload = await this.EncryptionProcessor.EncryptAsync( - streamPayload, - diagnosticsContext, - cancellationToken); - - ResponseMessage responseMessage = await this.Container.CreateItemStreamAsync( - streamPayload, - partitionKey, - requestOptions, - cancellationToken); - - responseMessage.Content = await this.EncryptionProcessor.DecryptAsync( - responseMessage.Content, - diagnosticsContext, - cancellationToken); - - return responseMessage; - } - public override Task> DeleteItemAsync( string id, PartitionKey partitionKey, ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.DeleteItemAsync( + return this.container.DeleteItemAsync( id, partitionKey, requestOptions, @@ -214,7 +128,7 @@ public override Task DeleteItemStreamAsync( ItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.DeleteItemStreamAsync( + return this.container.DeleteItemStreamAsync( id, partitionKey, requestOptions, @@ -261,27 +175,6 @@ public override async Task ReadItemStreamAsync( } } - private async Task ReadItemHelperAsync( - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - ResponseMessage responseMessage = await this.Container.ReadItemStreamAsync( - id, - partitionKey, - requestOptions, - cancellationToken); - - responseMessage.Content = await this.EncryptionProcessor.DecryptAsync( - responseMessage.Content, - diagnosticsContext, - cancellationToken); - - return responseMessage; - } - public override async Task> ReplaceItemAsync( T item, string id, @@ -354,39 +247,6 @@ public override async Task ReplaceItemStreamAsync( } } - private async Task ReplaceItemHelperAsync( - Stream streamPayload, - string id, - PartitionKey partitionKey, - ItemRequestOptions requestOptions, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - if (partitionKey == null) - { - throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); - } - - streamPayload = await this.EncryptionProcessor.EncryptAsync( - streamPayload, - diagnosticsContext, - cancellationToken); - - ResponseMessage responseMessage = await this.Container.ReplaceItemStreamAsync( - streamPayload, - id, - partitionKey, - requestOptions, - cancellationToken); - - responseMessage.Content = await this.EncryptionProcessor.DecryptAsync( - responseMessage.Content, - diagnosticsContext, - cancellationToken); - - return responseMessage; - } - public override async Task> UpsertItemAsync( T item, PartitionKey? partitionKey = null, @@ -445,43 +305,12 @@ public override async Task UpsertItemStreamAsync( } } - private async Task UpsertItemHelperAsync( - Stream streamPayload, - PartitionKey partitionKey, - ItemRequestOptions requestOptions, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - if (partitionKey == null) - { - throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); - } - - streamPayload = await this.EncryptionProcessor.EncryptAsync( - streamPayload, - diagnosticsContext, - cancellationToken); - - ResponseMessage responseMessage = await this.Container.UpsertItemStreamAsync( - streamPayload, - partitionKey, - requestOptions, - cancellationToken); - - responseMessage.Content = await this.EncryptionProcessor.DecryptAsync( - responseMessage.Content, - diagnosticsContext, - cancellationToken); - - return responseMessage; - } - public override TransactionalBatch CreateTransactionalBatch( PartitionKey partitionKey) { return new EncryptionTransactionalBatch( - this.Container.CreateTransactionalBatch(partitionKey), - this.EncryptionProcessor, + this.container.CreateTransactionalBatch(partitionKey), + this, this.CosmosSerializer); } @@ -489,7 +318,7 @@ public override Task DeleteContainerAsync( ContainerRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.DeleteContainerAsync( + return this.container.DeleteContainerAsync( requestOptions, cancellationToken); } @@ -498,7 +327,7 @@ public override Task DeleteContainerStreamAsync( ContainerRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.DeleteContainerStreamAsync( + return this.container.DeleteContainerStreamAsync( requestOptions, cancellationToken); } @@ -508,7 +337,7 @@ public override ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder( ChangesEstimationHandler estimationDelegate, TimeSpan? estimationPeriod = null) { - return this.Container.GetChangeFeedEstimatorBuilder( + return this.container.GetChangeFeedEstimatorBuilder( processorName, estimationDelegate, estimationPeriod); @@ -520,7 +349,7 @@ public override IOrderedQueryable GetItemLinqQueryable( QueryRequestOptions requestOptions = null, CosmosLinqSerializerOptions linqSerializerOptions = null) { - return this.Container.GetItemLinqQueryable( + return this.container.GetItemLinqQueryable( allowSynchronousQueryExecution, continuationToken, requestOptions, @@ -557,7 +386,7 @@ public override Task ReadContainerAsync( ContainerRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.ReadContainerAsync( + return this.container.ReadContainerAsync( requestOptions, cancellationToken); } @@ -566,7 +395,7 @@ public override Task ReadContainerStreamAsync( ContainerRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.ReadContainerStreamAsync( + return this.container.ReadContainerStreamAsync( requestOptions, cancellationToken); } @@ -574,14 +403,14 @@ public override Task ReadContainerStreamAsync( public override Task ReadThroughputAsync( CancellationToken cancellationToken = default) { - return this.Container.ReadThroughputAsync(cancellationToken); + return this.container.ReadThroughputAsync(cancellationToken); } public override Task ReadThroughputAsync( RequestOptions requestOptions, CancellationToken cancellationToken = default) { - return this.Container.ReadThroughputAsync( + return this.container.ReadThroughputAsync( requestOptions, cancellationToken); } @@ -591,7 +420,7 @@ public override Task ReplaceContainerAsync( ContainerRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.ReplaceContainerAsync( + return this.container.ReplaceContainerAsync( containerProperties, requestOptions, cancellationToken); @@ -602,7 +431,7 @@ public override Task ReplaceContainerStreamAsync( ContainerRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.ReplaceContainerStreamAsync( + return this.container.ReplaceContainerStreamAsync( containerProperties, requestOptions, cancellationToken); @@ -613,7 +442,7 @@ public override Task ReplaceThroughputAsync( RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.ReplaceThroughputAsync( + return this.container.ReplaceThroughputAsync( throughput, requestOptions, cancellationToken); @@ -624,12 +453,23 @@ public override FeedIterator GetItemQueryStreamIterator( string continuationToken = null, QueryRequestOptions requestOptions = null) { + QueryRequestOptions clonedRequestOptions; + if (requestOptions != null) + { + clonedRequestOptions = (QueryRequestOptions)requestOptions.ShallowCopy(); + } + else + { + clonedRequestOptions = new QueryRequestOptions(); + } + return new EncryptionFeedIterator( - this.Container.GetItemQueryStreamIterator( + this.container.GetItemQueryStreamIterator( queryDefinition, continuationToken, - requestOptions), - this.EncryptionProcessor); + clonedRequestOptions), + this, + clonedRequestOptions); } public override FeedIterator GetItemQueryStreamIterator( @@ -637,41 +477,23 @@ public override FeedIterator GetItemQueryStreamIterator( string continuationToken = null, QueryRequestOptions requestOptions = null) { + QueryRequestOptions clonedRequestOptions; + if (requestOptions != null) + { + clonedRequestOptions = (QueryRequestOptions)requestOptions.ShallowCopy(); + } + else + { + clonedRequestOptions = new QueryRequestOptions(); + } + return new EncryptionFeedIterator( - this.Container.GetItemQueryStreamIterator( + this.container.GetItemQueryStreamIterator( queryText, continuationToken, - requestOptions), - this.EncryptionProcessor); - } - - public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( - string processorName, - ChangesHandler onChangesDelegate) - { - CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(null); - using (diagnosticsContext.CreateScope("GetChangeFeedProcessorBuilder")) - { - return this.Container.GetChangeFeedProcessorBuilder( - processorName, - async (IReadOnlyCollection documents, CancellationToken cancellationToken) => - { - List decryptedItems = new List(documents.Count); - - foreach (JObject document in documents) - { - JObject decryptedDocument = await this.EncryptionProcessor.DecryptAsync( - document, - diagnosticsContext, - cancellationToken); - - decryptedItems.Add(decryptedDocument.ToObject()); - } - - // Call the original passed in delegate - await onChangesDelegate(decryptedItems, cancellationToken); - }); - } + clonedRequestOptions), + this, + clonedRequestOptions); } public override Task ReplaceThroughputAsync( @@ -679,7 +501,7 @@ public override Task ReplaceThroughputAsync( RequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - return this.Container.ReplaceThroughputAsync( + return this.container.ReplaceThroughputAsync( throughputProperties, requestOptions, cancellationToken); @@ -688,14 +510,14 @@ public override Task ReplaceThroughputAsync( public override Task> GetFeedRangesAsync( CancellationToken cancellationToken = default) { - return this.Container.GetFeedRangesAsync(cancellationToken); + return this.container.GetFeedRangesAsync(cancellationToken); } public override Task> GetPartitionKeyRangesAsync( FeedRange feedRange, CancellationToken cancellationToken = default) { - return this.Container.GetPartitionKeyRangesAsync(feedRange, cancellationToken); + return this.container.GetPartitionKeyRangesAsync(feedRange, cancellationToken); } public override FeedIterator GetItemQueryStreamIterator( @@ -704,13 +526,24 @@ public override FeedIterator GetItemQueryStreamIterator( string continuationToken, QueryRequestOptions requestOptions = null) { + QueryRequestOptions clonedRequestOptions; + if (requestOptions != null) + { + clonedRequestOptions = (QueryRequestOptions)requestOptions.ShallowCopy(); + } + else + { + clonedRequestOptions = new QueryRequestOptions(); + } + return new EncryptionFeedIterator( - this.Container.GetItemQueryStreamIterator( + this.container.GetItemQueryStreamIterator( feedRange, queryDefinition, continuationToken, - requestOptions), - this.EncryptionProcessor); + clonedRequestOptions), + this, + clonedRequestOptions); } public override FeedIterator GetItemQueryIterator( @@ -732,7 +565,7 @@ public override ChangeFeedEstimator GetChangeFeedEstimator( string processorName, Container leaseContainer) { - return this.Container.GetChangeFeedEstimator(processorName, leaseContainer); + return this.container.GetChangeFeedEstimator(processorName, leaseContainer); } public override FeedIterator GetChangeFeedStreamIterator( @@ -740,12 +573,23 @@ public override FeedIterator GetChangeFeedStreamIterator( ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions = null) { + ChangeFeedRequestOptions clonedchangeFeedRequestOptions; + if (changeFeedRequestOptions != null) + { + clonedchangeFeedRequestOptions = (ChangeFeedRequestOptions)changeFeedRequestOptions.ShallowCopy(); + } + else + { + clonedchangeFeedRequestOptions = new ChangeFeedRequestOptions(); + } + return new EncryptionFeedIterator( - this.Container.GetChangeFeedStreamIterator( + this.container.GetChangeFeedStreamIterator( changeFeedStartFrom, changeFeedMode, - changeFeedRequestOptions), - this.EncryptionProcessor); + clonedchangeFeedRequestOptions), + this, + clonedchangeFeedRequestOptions); } public override FeedIterator GetChangeFeedIterator( @@ -761,24 +605,739 @@ public override FeedIterator GetChangeFeedIterator( this.ResponseFactory); } - public override Task> PatchItemAsync( + public async override Task> PatchItemAsync( string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + ResponseMessage responseMessage = await this.PatchItemStreamAsync( + id, + partitionKey, + patchOperations, + requestOptions, + cancellationToken); + + return this.ResponseFactory.CreateItemResponse(responseMessage); } - public override Task PatchItemStreamAsync( + public async override Task PatchItemStreamAsync( string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions = null, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + if (partitionKey == null) + { + throw new ArgumentNullException(nameof(partitionKey)); + } + + if (patchOperations == null || + !patchOperations.Any()) + { + throw new ArgumentNullException(nameof(patchOperations)); + } + + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: null, + cancellationToken: cancellationToken); + + CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); + using (diagnosticsContext.CreateScope("PatchItem")) + { + List encryptedPatchOperations = await this.PatchItemHelperAsync( + patchOperations, + encryptionSettings, + cancellationToken); + + ResponseMessage responseMessage = await this.container.PatchItemStreamAsync( + id, + partitionKey, + encryptedPatchOperations, + requestOptions, + cancellationToken); + + responseMessage.Content = await EncryptionProcessor.DecryptAsync( + responseMessage.Content, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return responseMessage; + } + } + + private async Task> PatchItemHelperAsync( + IReadOnlyList patchOperations, + EncryptionSettings encryptionSettings, + CancellationToken cancellationToken = default) + { + List encryptedPatchOperations = new List(patchOperations.Count); + + foreach (PatchOperation patchOperation in patchOperations) + { + if (patchOperation.OperationType == PatchOperationType.Remove) + { + encryptedPatchOperations.Add(patchOperation); + continue; + } + + if (string.IsNullOrWhiteSpace(patchOperation.Path) || patchOperation.Path[0] != '/') + { + throw new ArgumentException($"Invalid path '{patchOperation.Path}'."); + } + + // get the top level path's encryption setting. + EncryptionSettingForProperty settingforProperty = encryptionSettings.GetEncryptionSettingForProperty( + patchOperation.Path.Split('/')[1]); + + // non-encrypted path + if (settingforProperty == null) + { + encryptedPatchOperations.Add(patchOperation); + continue; + } + else if (patchOperation.OperationType == PatchOperationType.Increment) + { + throw new InvalidOperationException($"Increment patch operation is not allowed for encrypted path '{patchOperation.Path}'."); + } + + if (!patchOperation.TrySerializeValueParameter(this.CosmosSerializer, out Stream valueParam)) + { + throw new ArgumentException($"Cannot serialize value parameter for operation: {patchOperation.OperationType}, path: {patchOperation.Path}."); + } + + Stream encryptedPropertyValue = await EncryptionProcessor.EncryptValueStreamAsync( + valueParam, + settingforProperty, + cancellationToken); + + switch (patchOperation.OperationType) + { + case PatchOperationType.Add: + encryptedPatchOperations.Add(PatchOperation.Add(patchOperation.Path, encryptedPropertyValue)); + break; + + case PatchOperationType.Replace: + encryptedPatchOperations.Add(PatchOperation.Replace(patchOperation.Path, encryptedPropertyValue)); + break; + + case PatchOperationType.Set: + encryptedPatchOperations.Add(PatchOperation.Set(patchOperation.Path, encryptedPropertyValue)); + break; + + default: + throw new NotSupportedException(nameof(patchOperation.OperationType)); + } + } + + return encryptedPatchOperations; + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangesHandler onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilder( + processorName, + async ( + IReadOnlyCollection documents, + CancellationToken cancellationToken) => + { + List decryptedItems = await this.DecryptChangeFeedDocumentsAsync( + documents, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(decryptedItems, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedHandler onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilder( + processorName, + async ( + ChangeFeedProcessorContext context, + IReadOnlyCollection documents, + CancellationToken cancellationToken) => + { + List decryptedItems = await this.DecryptChangeFeedDocumentsAsync( + documents, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptedItems, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedHandlerWithManualCheckpoint onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + processorName, + async ( + ChangeFeedProcessorContext context, + IReadOnlyCollection documents, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => + { + List decryptedItems = await this.DecryptChangeFeedDocumentsAsync( + documents, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptedItems, tryCheckpointAsync, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder( + string processorName, + ChangeFeedStreamHandler onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilder( + processorName, + async ( + ChangeFeedProcessorContext context, + Stream changes, + CancellationToken cancellationToken) => + { + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: null, + cancellationToken: cancellationToken); + + Stream decryptedChanges = await this.DeserializeAndDecryptResponseAsync( + changes, + encryptionSettings, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptedChanges, cancellationToken); + }); + } + + public override ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint( + string processorName, + ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate) + { + return this.container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + processorName, + async ( + ChangeFeedProcessorContext context, + Stream changes, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => + { + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: null, + cancellationToken: cancellationToken); + + Stream decryptedChanges = await this.DeserializeAndDecryptResponseAsync( + changes, + encryptionSettings, + cancellationToken); + + // Call the original passed in delegate + await onChangesDelegate(context, decryptedChanges, tryCheckpointAsync, cancellationToken); + }); + } + + public override Task ReadManyItemsStreamAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public override Task> ReadManyItemsAsync( + IReadOnlyList<(string id, PartitionKey partitionKey)> items, + ReadManyRequestOptions readManyRequestOptions = null, + CancellationToken cancellationToken = default) + { + throw new NotImplementedException(); + } + + public async Task GetOrUpdateEncryptionSettingsFromCacheAsync( + EncryptionSettings obsoleteEncryptionSettings, + CancellationToken cancellationToken) + { + return await this.encryptionSettingsByContainerName.GetAsync( + this.Id, + obsoleteValue: obsoleteEncryptionSettings, + singleValueInitFunc: () => EncryptionSettings.CreateAsync(this, cancellationToken), + cancellationToken: cancellationToken); + } + + /// + /// Returns a cloned copy of the passed RequestOptions if passed else creates a new ItemRequestOptions. + /// + /// Original ItemRequestOptions + /// ItemRequestOptions. + private static ItemRequestOptions GetClonedItemRequestOptions(ItemRequestOptions itemRequestOptions) + { + ItemRequestOptions clonedRequestOptions; + + if (itemRequestOptions != null) + { + clonedRequestOptions = (ItemRequestOptions)itemRequestOptions.ShallowCopy(); + } + else + { + clonedRequestOptions = new ItemRequestOptions(); + } + + return clonedRequestOptions; + } + + private async Task CreateItemHelperAsync( + Stream streamPayload, + PartitionKey partitionKey, + ItemRequestOptions requestOptions, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken, + bool isRetry = false) + { + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return await this.container.CreateItemStreamAsync( + streamPayload, + partitionKey, + requestOptions, + cancellationToken); + } + + streamPayload = await EncryptionProcessor.EncryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + ItemRequestOptions clonedRequestOptions = requestOptions; + + // only clone it on the first try. + if (!isRetry) + { + clonedRequestOptions = GetClonedItemRequestOptions(requestOptions); + } + + encryptionSettings.SetRequestHeaders(clonedRequestOptions); + + ResponseMessage responseMessage = await this.container.CreateItemStreamAsync( + streamPayload, + partitionKey, + clonedRequestOptions, + cancellationToken); + + // This handles the scenario where a container is deleted(say from different Client) and recreated with same Id but with different client encryption policy. + // The idea is to have the container Rid cached and sent out as part of RequestOptions with Container Rid set in "x-ms-cosmos-intended-collection-rid" header. + // So when the container being referenced here gets recreated we would end up with a stale encryption settings and container Rid and this would result in BadRequest( and a substatus 1024). + // This would allow us to refresh the encryption settings and Container Rid, on the premise that the container recreated could possibly be configured with a new encryption policy. + if (!isRetry && + responseMessage.StatusCode == HttpStatusCode.BadRequest && + string.Equals(responseMessage.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) + { + // Even though the streamPayload position is expected to be 0, + // because for MemoryStream we just use the underlying buffer to send over the wire rather than using the Stream APIs + // resetting it 0 to be on a safer side. + streamPayload.Position = 0; + + // Now the streamPayload itself is not disposed off(and hence safe to use it in the below call) since the stream that is passed to CreateItemStreamAsync is a MemoryStream and not the original Stream + // that the user has passed. The call to EncryptAsync reads out the stream(and processes it) and returns a MemoryStream which is eventually cloned in the + // Cosmos SDK and then used. This stream however is to be disposed off as part of ResponseMessage when this gets returned. + streamPayload = await this.DecryptStreamPayloadAndUpdateEncryptionSettingsAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + // we try to recreate the item with the StreamPayload(to be encrypted) now that the encryptionSettings would have been updated with latest values if any. + return await this.CreateItemHelperAsync( + streamPayload, + partitionKey, + clonedRequestOptions, + diagnosticsContext, + cancellationToken, + isRetry: true); + } + + responseMessage.Content = await EncryptionProcessor.DecryptAsync( + responseMessage.Content, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return responseMessage; + } + + private async Task ReadItemHelperAsync( + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken, + bool isRetry = false) + { + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return await this.container.ReadItemStreamAsync( + id, + partitionKey, + requestOptions, + cancellationToken); + } + + ItemRequestOptions clonedRequestOptions = requestOptions; + + // only clone it on the first try. + if (!isRetry) + { + clonedRequestOptions = GetClonedItemRequestOptions(requestOptions); + } + + encryptionSettings.SetRequestHeaders(clonedRequestOptions); + + ResponseMessage responseMessage = await this.container.ReadItemStreamAsync( + id, + partitionKey, + clonedRequestOptions, + cancellationToken); + + if (!isRetry && + responseMessage.StatusCode == HttpStatusCode.BadRequest && + string.Equals(responseMessage.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) + { + // get the latest encryption settings. + await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: encryptionSettings, + cancellationToken: cancellationToken); + + return await this.ReadItemHelperAsync( + id, + partitionKey, + clonedRequestOptions, + diagnosticsContext, + cancellationToken, + isRetry: true); + } + + responseMessage.Content = await EncryptionProcessor.DecryptAsync( + responseMessage.Content, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return responseMessage; + } + + private async Task ReplaceItemHelperAsync( + Stream streamPayload, + string id, + PartitionKey partitionKey, + ItemRequestOptions requestOptions, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken, + bool isRetry = false) + { + if (partitionKey == null) + { + throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); + } + + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return await this.container.ReplaceItemStreamAsync( + streamPayload, + id, + partitionKey, + requestOptions, + cancellationToken); + } + + streamPayload = await EncryptionProcessor.EncryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + ItemRequestOptions clonedRequestOptions = requestOptions; + + // only clone it on the first try. + if (!isRetry) + { + clonedRequestOptions = GetClonedItemRequestOptions(requestOptions); + } + + encryptionSettings.SetRequestHeaders(clonedRequestOptions); + + ResponseMessage responseMessage = await this.container.ReplaceItemStreamAsync( + streamPayload, + id, + partitionKey, + clonedRequestOptions, + cancellationToken); + + if (!isRetry && + responseMessage.StatusCode == HttpStatusCode.BadRequest && + string.Equals(responseMessage.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) + { + streamPayload.Position = 0; + streamPayload = await this.DecryptStreamPayloadAndUpdateEncryptionSettingsAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return await this.ReplaceItemHelperAsync( + streamPayload, + id, + partitionKey, + clonedRequestOptions, + diagnosticsContext, + cancellationToken, + isRetry: true); + } + + responseMessage.Content = await EncryptionProcessor.DecryptAsync( + responseMessage.Content, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return responseMessage; + } + + private async Task UpsertItemHelperAsync( + Stream streamPayload, + PartitionKey partitionKey, + ItemRequestOptions requestOptions, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken, + bool isRetry = false) + { + if (partitionKey == null) + { + throw new NotSupportedException($"{nameof(partitionKey)} cannot be null for operations using {nameof(EncryptionContainer)}."); + } + + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return await this.container.UpsertItemStreamAsync( + streamPayload, + partitionKey, + requestOptions, + cancellationToken); + } + + streamPayload = await EncryptionProcessor.EncryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + ItemRequestOptions clonedRequestOptions = requestOptions; + + // only clone it on the first try. + if (!isRetry) + { + clonedRequestOptions = GetClonedItemRequestOptions(requestOptions); + } + + encryptionSettings.SetRequestHeaders(clonedRequestOptions); + + ResponseMessage responseMessage = await this.container.UpsertItemStreamAsync( + streamPayload, + partitionKey, + clonedRequestOptions, + cancellationToken); + + if (!isRetry && + responseMessage.StatusCode == HttpStatusCode.BadRequest && + string.Equals(responseMessage.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) + { + streamPayload.Position = 0; + streamPayload = await this.DecryptStreamPayloadAndUpdateEncryptionSettingsAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return await this.UpsertItemHelperAsync( + streamPayload, + partitionKey, + clonedRequestOptions, + diagnosticsContext, + cancellationToken, + isRetry: true); + } + + responseMessage.Content = await EncryptionProcessor.DecryptAsync( + responseMessage.Content, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + return responseMessage; + } + + /// + /// This method takes in an encrypted Stream payload. + /// The streamPayload is decrypted with the same policy which was used to encrypt and and then the original plain stream payload is + /// returned which can be used to re-encrypt after the latest encryption settings is retrieved. + /// The method also updates the cached Encryption Settings with the latest value if any. + /// + /// Data encrypted with wrong encryption policy. + /// EncryptionSettings which was used to encrypt the payload. + /// Diagnostics context. + /// Cancellation token. + /// Returns the decrypted stream payload. + private async Task DecryptStreamPayloadAndUpdateEncryptionSettingsAsync( + Stream streamPayload, + EncryptionSettings encryptionSettings, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken) + { + streamPayload = await EncryptionProcessor.DecryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + // get the latest encryption settings. + await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: encryptionSettings, + cancellationToken: cancellationToken); + + return streamPayload; + } + + private async Task> DecryptChangeFeedDocumentsAsync( + IReadOnlyCollection documents, + CancellationToken cancellationToken) + { + List decryptedItems = new List(documents.Count); + + EncryptionSettings encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: null, + cancellationToken: cancellationToken); + + foreach (JObject document in documents) + { + try + { + JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( + document, + encryptionSettings, + cancellationToken); + + decryptedItems.Add(decryptedDocument.ToObject()); + } + + // we cannot rely currently on a specific exception, this is due to the fact that the run time issue can be variable, + // we can hit issue with either Json serialization say an item was not encrypted but the policy shows it as encrypted, + // or we could hit a MicrosoftDataEncryptionException from MDE lib etc. + catch (Exception) + { + // most likely the encryption policy has changed. + encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: encryptionSettings, + cancellationToken: cancellationToken); + + JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( + document, + encryptionSettings, + cancellationToken); + + decryptedItems.Add(decryptedDocument.ToObject()); + } + } + + return decryptedItems; + } + + internal async Task DeserializeAndDecryptResponseAsync( + Stream content, + EncryptionSettings encryptionSettings, + CancellationToken cancellationToken) + { + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return content; + } + + JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream(content); + JArray results = new JArray(); + + if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) + { + throw new InvalidOperationException("Feed Response body contract was violated. Feed response did not have an array of Documents. "); + } + + foreach (JToken value in documents) + { + if (value is not JObject document) + { + results.Add(value); + continue; + } + + try + { + JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( + document, + encryptionSettings, + cancellationToken); + + results.Add(decryptedDocument); + } + + // we cannot rely currently on a specific exception, this is due to the fact that the run time issue can be variable, + // we can hit issue with either Json serialization say an item was not encrypted but the policy shows it as encrypted, + // or we could hit a MicrosoftDataEncryptionException from MDE lib etc. + catch (Exception) + { + // most likely the encryption policy has changed. + encryptionSettings = await this.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: encryptionSettings, + cancellationToken: cancellationToken); + + JObject decryptedDocument = await EncryptionProcessor.DecryptAsync( + document, + encryptionSettings, + cancellationToken); + + results.Add(decryptedDocument); + } + } + + JObject decryptedResponse = new JObject(); + foreach (JProperty property in contentJObj.Properties()) + { + if (property.Name.Equals(Constants.DocumentsResourcePropertyName)) + { + decryptedResponse.Add(property.Name, (JToken)results); + } + else + { + decryptedResponse.Add(property.Name, property.Value); + } + } + + return EncryptionProcessor.BaseSerializer.ToStream(decryptedResponse); } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainerExtensions.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainerExtensions.cs index 247f7e93b7..b20758d1bc 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainerExtensions.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionContainerExtensions.cs @@ -40,11 +40,13 @@ public static async Task InitializeEncryptionAsync( { cancellationToken.ThrowIfCancellationRequested(); - if (container is EncryptionContainer encryptionContainer) + if (container is not EncryptionContainer encryptionContainer) { - await encryptionContainer.InitEncryptionContainerCacheIfNotInitAsync(cancellationToken); + throw new ArgumentOutOfRangeException($"{nameof(InitializeEncryptionAsync)} requires the use of an encryption - enabled client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } + await encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + return container; } @@ -109,7 +111,8 @@ public static FeedIterator ToEncryptionStreamIterator( return new EncryptionFeedIterator( query.ToStreamIterator(), - encryptionContainer.EncryptionProcessor); + encryptionContainer, + new RequestOptions()); } /// diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionCosmosClient.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionCosmosClient.cs index d089d64a83..509574aae2 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionCosmosClient.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionCosmosClient.cs @@ -17,102 +17,17 @@ internal sealed class EncryptionCosmosClient : CosmosClient { private readonly CosmosClient cosmosClient; - private readonly AsyncCache clientEncryptionPolicyCacheByContainerId; - private readonly AsyncCache clientEncryptionKeyPropertiesCacheByKeyId; public EncryptionCosmosClient(CosmosClient cosmosClient, EncryptionKeyStoreProvider encryptionKeyStoreProvider) { this.cosmosClient = cosmosClient ?? throw new ArgumentNullException(nameof(cosmosClient)); this.EncryptionKeyStoreProvider = encryptionKeyStoreProvider ?? throw new ArgumentNullException(nameof(encryptionKeyStoreProvider)); - this.clientEncryptionPolicyCacheByContainerId = new AsyncCache(); this.clientEncryptionKeyPropertiesCacheByKeyId = new AsyncCache(); } public EncryptionKeyStoreProvider EncryptionKeyStoreProvider { get; } - /// - /// Gets or Adds ClientEncryptionPolicy. The Cache gets seeded initially either via InitializeEncryptionAsync call on the container, - /// or during the the first request to create an item. - /// - /// The container handler to read the policies from. - /// cancellation token - /// force refresh the cache - /// task result - internal async Task GetClientEncryptionPolicyAsync( - Container container, - CancellationToken cancellationToken = default, - bool shouldForceRefresh = false) - { - if (container == null) - { - throw new ArgumentNullException(nameof(container)); - } - - // container Id is unique within a Database. - string cacheKey = container.Database.Id + "/" + container.Id; - - // cache it against Database and Container ID key. - return await this.clientEncryptionPolicyCacheByContainerId.GetAsync( - cacheKey, - obsoleteValue: null, - async () => - { - ContainerResponse containerResponse = await container.ReadContainerAsync(); - ClientEncryptionPolicy clientEncryptionPolicy = containerResponse.Resource.ClientEncryptionPolicy; - return clientEncryptionPolicy; - }, - cancellationToken, - forceRefresh: shouldForceRefresh); - } - - internal async Task GetClientEncryptionKeyPropertiesAsync( - string clientEncryptionKeyId, - Container container, - CancellationToken cancellationToken = default, - bool shouldForceRefresh = false) - { - if (container == null) - { - throw new ArgumentNullException(nameof(container)); - } - - // Client Encryption key Id is unique within a Database. - string cacheKey = container.Database.Id + "/" + clientEncryptionKeyId; - - return await this.clientEncryptionKeyPropertiesCacheByKeyId.GetAsync( - cacheKey, - obsoleteValue: null, - async () => await this.FetchClientEncryptionKeyPropertiesAsync(container, clientEncryptionKeyId, cancellationToken), - cancellationToken, - forceRefresh: shouldForceRefresh); - } - - internal async Task FetchClientEncryptionKeyPropertiesAsync( - Container container, - string clientEncryptionKeyId, - CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - ClientEncryptionKey clientEncryptionKey = container.Database.GetClientEncryptionKey(clientEncryptionKeyId); - try - { - return await clientEncryptionKey.ReadAsync(cancellationToken: cancellationToken); - } - catch (CosmosException ex) - { - if (ex.StatusCode == HttpStatusCode.NotFound) - { - throw new InvalidOperationException($"Encryption Based Container without Data Encryption Keys. Please make sure you have created the Client Encryption Keys:{ex.Message}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); - } - else - { - throw; - } - } - } - public override CosmosClientOptions ClientOptions => this.cosmosClient.ClientOptions; public override CosmosResponseFactory ResponseFactory => this.cosmosClient.ResponseFactory; @@ -254,5 +169,63 @@ protected override void Dispose(bool disposing) { this.cosmosClient.Dispose(); } + + public async Task GetClientEncryptionKeyPropertiesAsync( + string clientEncryptionKeyId, + EncryptionContainer encryptionContainer, + string databaseRid, + CancellationToken cancellationToken = default, + bool shouldForceRefresh = false) + { + if (encryptionContainer == null) + { + throw new ArgumentNullException(nameof(encryptionContainer)); + } + + if (string.IsNullOrEmpty(databaseRid)) + { + throw new ArgumentNullException(nameof(databaseRid)); + } + + if (string.IsNullOrEmpty(clientEncryptionKeyId)) + { + throw new ArgumentNullException(nameof(clientEncryptionKeyId)); + } + + // Client Encryption key Id is unique within a Database. + string cacheKey = databaseRid + "|" + clientEncryptionKeyId; + + return await this.clientEncryptionKeyPropertiesCacheByKeyId.GetAsync( + cacheKey, + obsoleteValue: null, + async () => await this.FetchClientEncryptionKeyPropertiesAsync(encryptionContainer, clientEncryptionKeyId, cancellationToken), + cancellationToken, + forceRefresh: shouldForceRefresh); + } + + private async Task FetchClientEncryptionKeyPropertiesAsync( + EncryptionContainer encryptionContainer, + string clientEncryptionKeyId, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + ClientEncryptionKey clientEncryptionKey = encryptionContainer.Database.GetClientEncryptionKey(clientEncryptionKeyId); + try + { + return await clientEncryptionKey.ReadAsync(cancellationToken: cancellationToken); + } + catch (CosmosException ex) + { + if (ex.StatusCode == HttpStatusCode.NotFound) + { + throw new InvalidOperationException($"Encryption Based Container without Client Encryption Keys. Please make sure you have created the Client Encryption Keys:{ex.Message}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); + } + else + { + throw; + } + } + } } } diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabase.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabase.cs index 27b80cd76a..610fab3f18 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabase.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabase.cs @@ -21,7 +21,7 @@ public EncryptionDatabase(Database database, EncryptionCosmosClient encryptionCo this.EncryptionCosmosClient = encryptionCosmosClient ?? throw new ArgumentNullException(nameof(encryptionCosmosClient)); } - internal EncryptionCosmosClient EncryptionCosmosClient { get; } + public EncryptionCosmosClient EncryptionCosmosClient { get; } public override string Id => this.database.Id; @@ -198,8 +198,10 @@ public override async Task CreateUserAsync( public override ContainerBuilder DefineContainer(string name, string partitionKeyPath) { - ContainerBuilder containerBuilder = this.database.DefineContainer(name, partitionKeyPath); - return containerBuilder; + return new ContainerBuilder( + this, + name, + partitionKeyPath); } public override async Task DeleteAsync( diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabaseExtensions.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabaseExtensions.cs index 7070bfed79..9611110e26 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabaseExtensions.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionDatabaseExtensions.cs @@ -74,6 +74,11 @@ public static async Task CreateClientEncryptionKeyA EncryptionKeyStoreProvider encryptionKeyStoreProvider = encryptionCosmosClient.EncryptionKeyStoreProvider; + if (!string.Equals(encryptionKeyWrapMetadata.Type, encryptionKeyStoreProvider.ProviderName)) + { + throw new ArgumentException("The EncryptionKeyWrapMetadata Type value does not match with the ProviderName of EncryptionKeyStoreProvider configured on the Client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); + } + KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate( encryptionKeyWrapMetadata.Name, encryptionKeyWrapMetadata.Value, @@ -156,6 +161,11 @@ public static async Task RewrapClientEncryptionKeyA EncryptionKeyStoreProvider encryptionKeyStoreProvider = encryptionCosmosClient.EncryptionKeyStoreProvider; + if (!string.Equals(newEncryptionKeyWrapMetadata.Type, encryptionKeyStoreProvider.ProviderName)) + { + throw new ArgumentException("The EncryptionKeyWrapMetadata Type value does not match with the ProviderName of EncryptionKeyStoreProvider configured on the Client. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); + } + ClientEncryptionKeyProperties clientEncryptionKeyProperties = await clientEncryptionKey.ReadAsync(cancellationToken: cancellationToken); RequestOptions requestOptions = new RequestOptions diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionFeedIterator.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionFeedIterator.cs index a8bfab2454..483c498454 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionFeedIterator.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionFeedIterator.cs @@ -6,21 +6,24 @@ namespace Microsoft.Azure.Cosmos.Encryption { using System; using System.IO; + using System.Net; using System.Threading; using System.Threading.Tasks; - using Newtonsoft.Json.Linq; internal sealed class EncryptionFeedIterator : FeedIterator { private readonly FeedIterator feedIterator; - private readonly EncryptionProcessor encryptionProcessor; + private readonly EncryptionContainer encryptionContainer; + private readonly RequestOptions requestOptions; public EncryptionFeedIterator( FeedIterator feedIterator, - EncryptionProcessor encryptionProcessor) + EncryptionContainer encryptionContainer, + RequestOptions requestOptions) { this.feedIterator = feedIterator ?? throw new ArgumentNullException(nameof(feedIterator)); - this.encryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); + this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer)); + this.requestOptions = requestOptions ?? throw new ArgumentNullException(nameof(requestOptions)); } public override bool HasMoreResults => this.feedIterator.HasMoreResults; @@ -30,13 +33,32 @@ public override async Task ReadNextAsync(CancellationToken canc CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null); using (diagnosticsContext.CreateScope("FeedIterator.ReadNext")) { + EncryptionSettings encryptionSettings = await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + encryptionSettings.SetRequestHeaders(this.requestOptions); + ResponseMessage responseMessage = await this.feedIterator.ReadNextAsync(cancellationToken); + // check for Bad Request and Wrong RID intended and update the cached RID and Client Encryption Policy. + if (responseMessage.StatusCode == HttpStatusCode.BadRequest + && string.Equals(responseMessage.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) + { + await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: encryptionSettings, + cancellationToken: cancellationToken); + + throw new CosmosException( + "Operation has failed due to a possible mismatch in Client Encryption Policy configured on the container. Please refer to https://aka.ms/CosmosClientEncryption for more details. " + responseMessage.ErrorMessage, + responseMessage.StatusCode, + int.Parse(Constants.IncorrectContainerRidSubStatus), + responseMessage.Headers.ActivityId, + responseMessage.Headers.RequestCharge); + } + if (responseMessage.IsSuccessStatusCode && responseMessage.Content != null) { - Stream decryptedContent = await this.DeserializeAndDecryptResponseAsync( + Stream decryptedContent = await this.encryptionContainer.DeserializeAndDecryptResponseAsync( responseMessage.Content, - diagnosticsContext, + encryptionSettings, cancellationToken); return new DecryptedResponseMessage(responseMessage, decryptedContent); @@ -45,50 +67,5 @@ public override async Task ReadNextAsync(CancellationToken canc return responseMessage; } } - - private async Task DeserializeAndDecryptResponseAsync( - Stream content, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - JObject contentJObj = EncryptionProcessor.BaseSerializer.FromStream(content); - JArray results = new JArray(); - - if (!(contentJObj.SelectToken(Constants.DocumentsResourcePropertyName) is JArray documents)) - { - throw new InvalidOperationException("Feed Response body contract was violated. Feed response did not have an array of Documents. "); - } - - foreach (JToken value in documents) - { - if (value is not JObject document) - { - results.Add(value); - continue; - } - - JObject decryptedDocument = await this.encryptionProcessor.DecryptAsync( - document, - diagnosticsContext, - cancellationToken); - - results.Add(decryptedDocument); - } - - JObject decryptedResponse = new JObject(); - foreach (JProperty property in contentJObj.Properties()) - { - if (property.Name.Equals(Constants.DocumentsResourcePropertyName)) - { - decryptedResponse.Add(property.Name, (JToken)results); - } - else - { - decryptedResponse.Add(property.Name, property.Value); - } - } - - return EncryptionProcessor.BaseSerializer.ToStream(decryptedResponse); - } } } diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs index 383a19e87a..6b2bca374d 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionProcessor.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Encryption { using System; - using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -17,221 +16,218 @@ namespace Microsoft.Azure.Cosmos.Encryption using Newtonsoft.Json; using Newtonsoft.Json.Linq; - internal sealed class EncryptionProcessor + internal static class EncryptionProcessor { - private bool isEncryptionSettingsInitDone; - - private static readonly SemaphoreSlim CacheInitSema = new SemaphoreSlim(1, 1); - - /// - /// Gets the container that has items which are to be encrypted. - /// - public Container Container { get; } - - /// - /// Gets the provider that allows interaction with the master keys. - /// - public EncryptionKeyStoreProvider EncryptionKeyStoreProvider => this.EncryptionCosmosClient.EncryptionKeyStoreProvider; - - public ClientEncryptionPolicy ClientEncryptionPolicy { get; private set; } - - public EncryptionCosmosClient EncryptionCosmosClient { get; } - internal static readonly CosmosJsonDotNetSerializer BaseSerializer = new CosmosJsonDotNetSerializer( new JsonSerializerSettings() { DateParseHandling = DateParseHandling.None, }); - internal EncryptionSettings EncryptionSettings { get; } + private static readonly SqlSerializerFactory SqlSerializerFactory = SqlSerializerFactory.Default; - public EncryptionProcessor( - Container container, - EncryptionCosmosClient encryptionCosmosClient) + // UTF-8 Encoding + private static readonly SqlVarCharSerializer SqlVarcharSerializer = new SqlVarCharSerializer(size: -1, codePageCharacterEncoding: 65001); + + private enum TypeMarker : byte { - this.Container = container ?? throw new ArgumentNullException(nameof(container)); - this.EncryptionCosmosClient = encryptionCosmosClient ?? throw new ArgumentNullException(nameof(encryptionCosmosClient)); - this.isEncryptionSettingsInitDone = false; - this.EncryptionSettings = new EncryptionSettings(this); + Null = 1, // not used + Boolean = 2, + Double = 3, + Long = 4, + String = 5, } - /// - /// Builds up and caches the Encryption Setting by getting the cached entries of Client Encryption Policy and the corresponding keys. - /// Sets up the MDE Algorithm for encryption and decryption by initializing the KeyEncryptionKey and ProtectedDataEncryptionKey. - /// - /// cancellation token - /// Task - internal async Task InitializeEncryptionSettingsAsync(CancellationToken cancellationToken = default) + /// + /// If there isn't any PathsToEncrypt, input stream will be returned without any modification. + /// Else input stream will be disposed, and a new stream is returned. + /// In case of an exception, input stream won't be disposed, but position will be end of stream. + /// + public static async Task EncryptAsync( + Stream input, + EncryptionSettings encryptionSettings, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - - if (this.isEncryptionSettingsInitDone) + if (input == null) { - throw new InvalidOperationException("The Encrypton Processor has already been initialized. "); + throw new ArgumentNullException(nameof(input)); } - // fetch the cached policy. - this.ClientEncryptionPolicy = await this.EncryptionCosmosClient.GetClientEncryptionPolicyAsync( - container: this.Container, - cancellationToken: cancellationToken, - shouldForceRefresh: false); + Debug.Assert(diagnosticsContext != null); - // no policy was configured. - if (this.ClientEncryptionPolicy == null) + JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream(input); + + foreach (string propertyName in encryptionSettings.PropertiesToEncrypt) { - this.isEncryptionSettingsInitDone = true; - return; + // possibly a wrong path configured in the Client Encryption Policy, ignore. + JProperty propertyToEncrypt = itemJObj.Property(propertyName); + if (propertyToEncrypt == null) + { + continue; + } + + EncryptionSettingForProperty settingforProperty = encryptionSettings.GetEncryptionSettingForProperty(propertyName); + + if (settingforProperty == null) + { + throw new ArgumentException($"Invalid Encryption Setting for the Property:{propertyName}. "); + } + + await EncryptJTokenAsync( + propertyToEncrypt.Value, + settingforProperty, + cancellationToken); } - // update the property level setting. - foreach (ClientEncryptionIncludedPath propertyToEncrypt in this.ClientEncryptionPolicy.IncludedPaths) + input.Dispose(); + return EncryptionProcessor.BaseSerializer.ToStream(itemJObj); + } + + /// + /// If there isn't any data that needs to be decrypted, input stream will be returned without any modification. + /// Else input stream will be disposed, and a new stream is returned. + /// In case of an exception, input stream won't be disposed, but position will be end of stream. + /// + public static async Task DecryptAsync( + Stream input, + EncryptionSettings encryptionSettings, + CosmosDiagnosticsContext diagnosticsContext, + CancellationToken cancellationToken) + { + if (input == null) { - EncryptionType encryptionType = this.EncryptionSettings.GetEncryptionTypeForProperty(propertyToEncrypt); + return input; + } - EncryptionSettingForProperty encryptionSettingsForProperty = new EncryptionSettingForProperty( - propertyToEncrypt.ClientEncryptionKeyId, - encryptionType, - this); + Debug.Assert(input.CanSeek); + Debug.Assert(diagnosticsContext != null); - string propertyName = propertyToEncrypt.Path.Substring(1); + JObject itemJObj = RetrieveItem(input); - this.EncryptionSettings.SetEncryptionSettingForProperty( - propertyName, - encryptionSettingsForProperty); - } + await DecryptObjectAsync( + itemJObj, + encryptionSettings, + diagnosticsContext, + cancellationToken); + + input.Dispose(); + return EncryptionProcessor.BaseSerializer.ToStream(itemJObj); + } + + public static async Task DecryptAsync( + JObject document, + EncryptionSettings encryptionSettings, + CancellationToken cancellationToken) + { + Debug.Assert(document != null); + + await DecryptObjectAsync( + document, + encryptionSettings, + CosmosDiagnosticsContext.Create(null), + cancellationToken); - this.isEncryptionSettingsInitDone = true; + return document; } - /// - /// Initializes the Encryption Setting for the processor if not initialized or if shouldForceRefresh is true. - /// - /// (Optional) Token to cancel the operation. - /// Task to await. - internal async Task InitEncryptionSettingsIfNotInitializedAsync(CancellationToken cancellationToken = default) + internal static async Task EncryptValueStreamAsync( + Stream valueStream, + EncryptionSettingForProperty settingsForProperty, + CancellationToken cancellationToken) { - if (this.isEncryptionSettingsInitDone) + if (valueStream == null) { - return; + throw new ArgumentNullException(nameof(valueStream)); } - if (await CacheInitSema.WaitAsync(-1)) + if (settingsForProperty == null) { - if (!this.isEncryptionSettingsInitDone) - { - try - { - await this.InitializeEncryptionSettingsAsync(cancellationToken); - } - finally - { - CacheInitSema.Release(1); - } - } - else - { - CacheInitSema.Release(1); - } + throw new ArgumentNullException(nameof(settingsForProperty)); + } + + JToken propertyValueToEncrypt = EncryptionProcessor.BaseSerializer.FromStream(valueStream); + + JToken encryptedPropertyValue = propertyValueToEncrypt; + if (propertyValueToEncrypt.Type == JTokenType.Object || propertyValueToEncrypt.Type == JTokenType.Array) + { + await EncryptJTokenAsync(encryptedPropertyValue, settingsForProperty, cancellationToken); } + else + { + encryptedPropertyValue = await SerializeAndEncryptValueAsync(propertyValueToEncrypt, settingsForProperty, cancellationToken); + } + + return EncryptionProcessor.BaseSerializer.ToStream(encryptedPropertyValue); + } + + private static (TypeMarker, byte[]) Serialize(JToken propertyValue) + { + return propertyValue.Type switch + { + JTokenType.Boolean => (TypeMarker.Boolean, SqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())), + JTokenType.Float => (TypeMarker.Double, SqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())), + JTokenType.Integer => (TypeMarker.Long, SqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())), + JTokenType.String => (TypeMarker.String, SqlVarcharSerializer.Serialize(propertyValue.ToObject())), + _ => throw new InvalidOperationException($"Invalid or Unsupported Data Type Passed : {propertyValue.Type}. "), + }; + } + + private static JToken DeserializeAndAddProperty( + byte[] serializedBytes, + TypeMarker typeMarker) + { + return typeMarker switch + { + TypeMarker.Boolean => SqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes), + TypeMarker.Double => SqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes), + TypeMarker.Long => SqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes), + TypeMarker.String => SqlVarcharSerializer.Deserialize(serializedBytes), + _ => throw new InvalidOperationException($"Invalid or Unsupported Data Type Passed : {typeMarker}. "), + }; } - private void EncryptProperty( - JObject itemJObj, - JToken propertyValue, - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm) + private static async Task EncryptJTokenAsync( + JToken jTokenToEncrypt, + EncryptionSettingForProperty encryptionSettingForProperty, + CancellationToken cancellationToken) { - /* Top Level can be an Object*/ - if (propertyValue.Type == JTokenType.Object) + // Top Level can be an Object + if (jTokenToEncrypt.Type == JTokenType.Object) { - foreach (JProperty jProperty in propertyValue.Children()) + foreach (JProperty jProperty in jTokenToEncrypt.Children()) { - if (jProperty.Value.Type == JTokenType.Object || jProperty.Value.Type == JTokenType.Array) - { - this.EncryptProperty( - itemJObj, - jProperty.Value, - aeadAes256CbcHmac256EncryptionAlgorithm); - } - else - { - jProperty.Value = this.SerializeAndEncryptValue(jProperty.Value, aeadAes256CbcHmac256EncryptionAlgorithm); - } + await EncryptJTokenAsync( + jProperty.Value, + encryptionSettingForProperty, + cancellationToken); } } - else if (propertyValue.Type == JTokenType.Array) + else if (jTokenToEncrypt.Type == JTokenType.Array) { - if (propertyValue.Children().Any()) + if (jTokenToEncrypt.Children().Any()) { - // objects as array elements. - if (propertyValue.Children().First().Type == JTokenType.Object) + for (int i = 0; i < jTokenToEncrypt.Count(); i++) { - foreach (JObject arrayjObject in propertyValue.Children()) - { - foreach (JProperty jProperty in arrayjObject.Properties()) - { - if (jProperty.Value.Type == JTokenType.Object || jProperty.Value.Type == JTokenType.Array) - { - this.EncryptProperty( - itemJObj, - jProperty.Value, - aeadAes256CbcHmac256EncryptionAlgorithm); - } - - // primitive type - else - { - jProperty.Value = this.SerializeAndEncryptValue(jProperty.Value, aeadAes256CbcHmac256EncryptionAlgorithm); - } - } - } - } - - // array as elements. - else if (propertyValue.Children().First().Type == JTokenType.Array) - { - foreach (JArray jArray in propertyValue.Value()) - { - for (int i = 0; i < jArray.Count(); i++) - { - // iterates over individual elements - if (jArray[i].Type == JTokenType.Object || jArray[i].Type == JTokenType.Array) - { - this.EncryptProperty( - itemJObj, - jArray[i], - aeadAes256CbcHmac256EncryptionAlgorithm); - } - - // primitive type - else - { - jArray[i] = this.SerializeAndEncryptValue(jArray[i], aeadAes256CbcHmac256EncryptionAlgorithm); - } - } - } - } - - // array of primitive types. - else - { - for (int i = 0; i < propertyValue.Count(); i++) - { - propertyValue[i] = this.SerializeAndEncryptValue(propertyValue[i], aeadAes256CbcHmac256EncryptionAlgorithm); - } + await EncryptJTokenAsync( + jTokenToEncrypt[i], + encryptionSettingForProperty, + cancellationToken); } } } else { - itemJObj.Property(propertyValue.Path).Value = this.SerializeAndEncryptValue( - itemJObj.Property(propertyValue.Path).Value, - aeadAes256CbcHmac256EncryptionAlgorithm); + jTokenToEncrypt.Replace(await SerializeAndEncryptValueAsync(jTokenToEncrypt, encryptionSettingForProperty, cancellationToken)); } + + return; } - private JToken SerializeAndEncryptValue( + private static async Task SerializeAndEncryptValueAsync( JToken jToken, - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm) + EncryptionSettingForProperty encryptionSettingForProperty, + CancellationToken cancellationToken) { JToken propertyValueToEncrypt = jToken; @@ -242,11 +238,12 @@ private JToken SerializeAndEncryptValue( (TypeMarker typeMarker, byte[] plainText) = Serialize(propertyValueToEncrypt); + AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await encryptionSettingForProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken: cancellationToken); byte[] cipherText = aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt(plainText); if (cipherText == null) { - throw new InvalidOperationException($"{nameof(this.SerializeAndEncryptValue)} returned null cipherText from {nameof(aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt)}. "); + throw new InvalidOperationException($"{nameof(SerializeAndEncryptValueAsync)} returned null cipherText from {nameof(aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt)}. "); } byte[] cipherTextWithTypeMarker = new byte[cipherText.Length + 1]; @@ -255,64 +252,10 @@ private JToken SerializeAndEncryptValue( return cipherTextWithTypeMarker; } - /// - /// If there isn't any PathsToEncrypt, input stream will be returned without any modification. - /// Else input stream will be disposed, and a new stream is returned. - /// In case of an exception, input stream won't be disposed, but position will be end of stream. - /// - public async Task EncryptAsync( - Stream input, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - Debug.Assert(diagnosticsContext != null); - - await this.InitEncryptionSettingsIfNotInitializedAsync(cancellationToken); - - if (this.ClientEncryptionPolicy == null) - { - return input; - } - - JObject itemJObj = EncryptionProcessor.BaseSerializer.FromStream(input); - - foreach (ClientEncryptionIncludedPath pathToEncrypt in this.ClientEncryptionPolicy.IncludedPaths) - { - string propertyName = pathToEncrypt.Path.Substring(1); - - // possibly a wrong path configured in the Client Encryption Policy, ignore. - if (!itemJObj.TryGetValue(propertyName, out JToken propertyValue)) - { - continue; - } - - EncryptionSettingForProperty settingforProperty = await this.EncryptionSettings.GetEncryptionSettingForPropertyAsync(propertyName,cancellationToken); - - if (settingforProperty == null) - { - throw new ArgumentException($"Invalid Encryption Setting for the Property:{propertyName}. "); - } - - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingforProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken: cancellationToken); - - this.EncryptProperty( - itemJObj, - propertyValue, - aeadAes256CbcHmac256EncryptionAlgorithm); - } - - input.Dispose(); - return EncryptionProcessor.BaseSerializer.ToStream(itemJObj); - } - - private JToken DecryptAndDeserializeValue( + private static async Task DecryptAndDeserializeValueAsync( JToken jToken, - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm) + EncryptionSettingForProperty encryptionSettingForProperty, + CancellationToken cancellationToken) { byte[] cipherTextWithTypeMarker = jToken.ToObject(); @@ -324,11 +267,12 @@ private JToken DecryptAndDeserializeValue( byte[] cipherText = new byte[cipherTextWithTypeMarker.Length - 1]; Buffer.BlockCopy(cipherTextWithTypeMarker, 1, cipherText, 0, cipherTextWithTypeMarker.Length - 1); + AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await encryptionSettingForProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken: cancellationToken); byte[] plainText = aeadAes256CbcHmac256EncryptionAlgorithm.Decrypt(cipherText); if (plainText == null) { - throw new InvalidOperationException($"{nameof(this.DecryptAndDeserializeValue)} returned null plainText from {nameof(aeadAes256CbcHmac256EncryptionAlgorithm.Decrypt)}. "); + throw new InvalidOperationException($"{nameof(DecryptAndDeserializeValueAsync)} returned null plainText from {nameof(aeadAes256CbcHmac256EncryptionAlgorithm.Decrypt)}. "); } return DeserializeAndAddProperty( @@ -336,196 +280,74 @@ private JToken DecryptAndDeserializeValue( (TypeMarker)cipherTextWithTypeMarker[0]); } - private void DecryptProperty( - JObject itemJObj, - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm, - string propertyName, - JToken propertyValue) + private static async Task DecryptJTokenAsync( + JToken jTokenToDecrypt, + EncryptionSettingForProperty encryptionSettingForProperty, + CancellationToken cancellationToken) { - if (propertyValue.Type == JTokenType.Object) + if (jTokenToDecrypt.Type == JTokenType.Object) { - foreach (JProperty jProperty in propertyValue.Children()) + foreach (JProperty jProperty in jTokenToDecrypt.Children()) { - if (jProperty.Value.Type == JTokenType.Object || jProperty.Value.Type == JTokenType.Array) - { - this.DecryptProperty( - itemJObj, - aeadAes256CbcHmac256EncryptionAlgorithm, - jProperty.Name, - jProperty.Value); - } - else - { - jProperty.Value = this.DecryptAndDeserializeValue( - jProperty.Value, - aeadAes256CbcHmac256EncryptionAlgorithm); - } + await DecryptJTokenAsync( + jProperty.Value, + encryptionSettingForProperty, + cancellationToken); } } - else if (propertyValue.Type == JTokenType.Array) + else if (jTokenToDecrypt.Type == JTokenType.Array) { - if (propertyValue.Children().Any()) + if (jTokenToDecrypt.Children().Any()) { - if (propertyValue.Children().First().Type == JTokenType.Object) - { - foreach (JObject arrayjObject in propertyValue.Children()) - { - foreach (JProperty jProperty in arrayjObject.Properties()) - { - if (jProperty.Value.Type == JTokenType.Object || jProperty.Value.Type == JTokenType.Array) - { - this.DecryptProperty( - itemJObj, - aeadAes256CbcHmac256EncryptionAlgorithm, - jProperty.Name, - jProperty.Value); - } - else - { - jProperty.Value = this.DecryptAndDeserializeValue( - jProperty.Value, - aeadAes256CbcHmac256EncryptionAlgorithm); - } - } - } - } - else if (propertyValue.Children().First().Type == JTokenType.Array) + for (int i = 0; i < jTokenToDecrypt.Count(); i++) { - foreach (JArray jArray in propertyValue.Value()) - { - for (int i = 0; i < jArray.Count(); i++) - { - // iterates over individual elements - if (jArray[i].Type == JTokenType.Object || jArray[i].Type == JTokenType.Array) - { - this.DecryptProperty( - itemJObj, - aeadAes256CbcHmac256EncryptionAlgorithm, - jArray[i].Path, - jArray[i]); - } - else - { - jArray[i] = this.DecryptAndDeserializeValue( - jArray[i], - aeadAes256CbcHmac256EncryptionAlgorithm); - } - } - } - } - - // primitive type - else - { - for (int i = 0; i < propertyValue.Count(); i++) - { - propertyValue[i] = this.DecryptAndDeserializeValue( - propertyValue[i], - aeadAes256CbcHmac256EncryptionAlgorithm); - } + await DecryptJTokenAsync( + jTokenToDecrypt[i], + encryptionSettingForProperty, + cancellationToken); } } } else { - itemJObj.Property(propertyName).Value = this.DecryptAndDeserializeValue( - itemJObj.Property(propertyName).Value, - aeadAes256CbcHmac256EncryptionAlgorithm); + jTokenToDecrypt.Replace(await DecryptAndDeserializeValueAsync( + jTokenToDecrypt, + encryptionSettingForProperty, + cancellationToken)); } } - private async Task DecryptObjectAsync( + private static async Task DecryptObjectAsync( JObject document, + EncryptionSettings encryptionSettings, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { Debug.Assert(diagnosticsContext != null); - foreach (ClientEncryptionIncludedPath path in this.ClientEncryptionPolicy.IncludedPaths) + foreach (string propertyName in encryptionSettings.PropertiesToEncrypt) { - if (document.TryGetValue(path.Path.Substring(1), out JToken propertyValue)) + JProperty propertyToDecrypt = document.Property(propertyName); + if (propertyToDecrypt != null) { - string propertyName = path.Path.Substring(1); - EncryptionSettingForProperty settingsForProperty = await this.EncryptionSettings.GetEncryptionSettingForPropertyAsync(propertyName, cancellationToken); + EncryptionSettingForProperty settingsForProperty = encryptionSettings.GetEncryptionSettingForProperty(propertyName); if (settingsForProperty == null) { throw new ArgumentException($"Invalid Encryption Setting for Property:{propertyName}. "); } - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingsForProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken: cancellationToken); - - this.DecryptProperty( - document, - aeadAes256CbcHmac256EncryptionAlgorithm, - propertyName, - propertyValue); + await DecryptJTokenAsync( + propertyToDecrypt.Value, + settingsForProperty, + cancellationToken); } } return; } - /// - /// If there isn't any data that needs to be decrypted, input stream will be returned without any modification. - /// Else input stream will be disposed, and a new stream is returned. - /// In case of an exception, input stream won't be disposed, but position will be end of stream. - /// - public async Task DecryptAsync( - Stream input, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - if (input == null) - { - return input; - } - - Debug.Assert(input.CanSeek); - Debug.Assert(diagnosticsContext != null); - - await this.InitEncryptionSettingsIfNotInitializedAsync(cancellationToken); - - if (this.ClientEncryptionPolicy == null) - { - input.Position = 0; - return input; - } - - JObject itemJObj = this.RetrieveItem(input); - - await this.DecryptObjectAsync( - itemJObj, - diagnosticsContext, - cancellationToken); - - input.Dispose(); - return EncryptionProcessor.BaseSerializer.ToStream(itemJObj); - } - - public async Task DecryptAsync( - JObject document, - CosmosDiagnosticsContext diagnosticsContext, - CancellationToken cancellationToken) - { - Debug.Assert(document != null); - - await this.InitEncryptionSettingsIfNotInitializedAsync(cancellationToken); - - if (this.ClientEncryptionPolicy == null) - { - return document; - } - - await this.DecryptObjectAsync( - document, - diagnosticsContext, - cancellationToken); - - return document; - } - - private JObject RetrieveItem( + private static JObject RetrieveItem( Stream input) { Debug.Assert(input != null); @@ -544,50 +366,5 @@ private JObject RetrieveItem( return itemJObj; } - - internal static (TypeMarker, byte[]) Serialize(JToken propertyValue) - { - SqlSerializerFactory sqlSerializerFactory = new SqlSerializerFactory(); - - // UTF-8 Encoding - SqlVarCharSerializer sqlVarcharSerializer = new SqlVarCharSerializer(size: -1, codePageCharacterEncoding: 65001); - - return propertyValue.Type switch - { - JTokenType.Boolean => (TypeMarker.Boolean, sqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())), - JTokenType.Float => (TypeMarker.Double, sqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())), - JTokenType.Integer => (TypeMarker.Long, sqlSerializerFactory.GetDefaultSerializer().Serialize(propertyValue.ToObject())), - JTokenType.String => (TypeMarker.String, sqlVarcharSerializer.Serialize(propertyValue.ToObject())), - _ => throw new InvalidOperationException($"Invalid or Unsupported Data Type Passed : {propertyValue.Type}. "), - }; - } - - internal static JToken DeserializeAndAddProperty( - byte[] serializedBytes, - TypeMarker typeMarker) - { - SqlSerializerFactory sqlSerializerFactory = new SqlSerializerFactory(); - - // UTF-8 Encoding - SqlVarCharSerializer sqlVarcharSerializer = new SqlVarCharSerializer(size: -1, codePageCharacterEncoding: 65001); - - return typeMarker switch - { - TypeMarker.Boolean => sqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes), - TypeMarker.Double => sqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes), - TypeMarker.Long => sqlSerializerFactory.GetDefaultSerializer().Deserialize(serializedBytes), - TypeMarker.String => sqlVarcharSerializer.Deserialize(serializedBytes), - _ => throw new InvalidOperationException($"Invalid or Unsupported Data Type Passed : {typeMarker}. "), - }; - } - - internal enum TypeMarker : byte - { - Null = 1, // not used - Boolean = 2, - Double = 3, - Long = 4, - String = 5, - } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettingForProperty.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettingForProperty.cs index 2aed12a903..cabccc4c56 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettingForProperty.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettingForProperty.cs @@ -17,22 +17,29 @@ internal sealed class EncryptionSettingForProperty public EncryptionType EncryptionType { get; } - public EncryptionProcessor EncryptionProcessor { get; } + private readonly string databaseRid; - public EncryptionSettingForProperty(string clientEncryptionKeyId, EncryptionType encryptionType, EncryptionProcessor encryptionProcessor) + private readonly EncryptionContainer encryptionContainer; + + public EncryptionSettingForProperty( + string clientEncryptionKeyId, + EncryptionType encryptionType, + EncryptionContainer encryptionContainer, + string databaseRid) { - this.ClientEncryptionKeyId = clientEncryptionKeyId ?? throw new ArgumentNullException(nameof(clientEncryptionKeyId)); + this.ClientEncryptionKeyId = string.IsNullOrEmpty(clientEncryptionKeyId) ? throw new ArgumentNullException(nameof(clientEncryptionKeyId)) : clientEncryptionKeyId; this.EncryptionType = encryptionType; - this.EncryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); + this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer)); + this.databaseRid = string.IsNullOrEmpty(databaseRid) ? throw new ArgumentNullException(nameof(databaseRid)) : databaseRid; } - internal async Task BuildEncryptionAlgorithmForSettingAsync(CancellationToken cancellationToken) + public async Task BuildEncryptionAlgorithmForSettingAsync(CancellationToken cancellationToken) { - ClientEncryptionKeyProperties clientEncryptionKeyProperties = await this.EncryptionProcessor.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( + ClientEncryptionKeyProperties clientEncryptionKeyProperties = await this.encryptionContainer.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( clientEncryptionKeyId: this.ClientEncryptionKeyId, - container: this.EncryptionProcessor.Container, - cancellationToken: cancellationToken, - shouldForceRefresh: false); + encryptionContainer: this.encryptionContainer, + databaseRid: this.databaseRid, + cancellationToken: cancellationToken); ProtectedDataEncryptionKey protectedDataEncryptionKey; @@ -42,7 +49,7 @@ internal async Task BuildEncryptionAlgo // Here a request is sent out to unwrap using the Master Key configured via the Key Encryption Key. protectedDataEncryptionKey = this.BuildProtectedDataEncryptionKey( clientEncryptionKeyProperties, - this.EncryptionProcessor.EncryptionKeyStoreProvider, + this.encryptionContainer.EncryptionCosmosClient.EncryptionKeyStoreProvider, this.ClientEncryptionKeyId); } catch (RequestFailedException ex) @@ -52,16 +59,17 @@ internal async Task BuildEncryptionAlgo // This is based on the AKV provider implementaion so we expect a RequestFailedException in case other providers are used in unwrap implementation. if (ex.Status == (int)HttpStatusCode.Forbidden) { - clientEncryptionKeyProperties = await this.EncryptionProcessor.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( + clientEncryptionKeyProperties = await this.encryptionContainer.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( clientEncryptionKeyId: this.ClientEncryptionKeyId, - container: this.EncryptionProcessor.Container, + encryptionContainer: this.encryptionContainer, + databaseRid: this.databaseRid, cancellationToken: cancellationToken, shouldForceRefresh: true); // just bail out if this fails. protectedDataEncryptionKey = this.BuildProtectedDataEncryptionKey( clientEncryptionKeyProperties, - this.EncryptionProcessor.EncryptionKeyStoreProvider, + this.encryptionContainer.EncryptionCosmosClient.EncryptionKeyStoreProvider, this.ClientEncryptionKeyId); } else @@ -77,7 +85,7 @@ internal async Task BuildEncryptionAlgo return aeadAes256CbcHmac256EncryptionAlgorithm; } - internal ProtectedDataEncryptionKey BuildProtectedDataEncryptionKey( + private ProtectedDataEncryptionKey BuildProtectedDataEncryptionKey( ClientEncryptionKeyProperties clientEncryptionKeyProperties, EncryptionKeyStoreProvider encryptionKeyStoreProvider, string keyId) diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettings.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettings.cs index 6922be863e..36a0e3b906 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettings.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionSettings.cs @@ -5,87 +5,130 @@ namespace Microsoft.Azure.Cosmos.Encryption { using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Encryption.Cryptography; internal sealed class EncryptionSettings { - internal AsyncCache EncryptionSettingCacheByPropertyName { get; } = new AsyncCache(); + // TODO: Good to have constants available in the Cosmos SDK. Tracked via https://github.com/Azure/azure-cosmos-dotnet-v3/issues/2431 + private const string IntendedCollectionHeader = "x-ms-cosmos-intended-collection-rid"; - public EncryptionProcessor EncryptionProcessor { get; } + private const string IsClientEncryptedHeader = "x-ms-cosmos-is-client-encrypted"; - public EncryptionSettings(EncryptionProcessor encryptionProcessor) + private readonly Dictionary encryptionSettingsDictByPropertyName; + + public string ContainerRidValue { get; } + + public IEnumerable PropertiesToEncrypt { get; } + + public static Task CreateAsync(EncryptionContainer encryptionContainer, CancellationToken cancellationToken) { - this.EncryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); + return InitializeEncryptionSettingsAsync(encryptionContainer, cancellationToken); } - internal async Task GetEncryptionSettingForPropertyAsync( - string propertyName, - CancellationToken cancellationToken) + public EncryptionSettingForProperty GetEncryptionSettingForProperty(string propertyName) { - EncryptionSettingForProperty encryptionSettingsForProperty = await this.EncryptionSettingCacheByPropertyName.GetAsync( - propertyName, - obsoleteValue: null, - async () => await this.FetchEncryptionSettingForPropertyAsync(propertyName, cancellationToken), - cancellationToken); + this.encryptionSettingsDictByPropertyName.TryGetValue(propertyName, out EncryptionSettingForProperty encryptionSettingsForProperty); + + return encryptionSettingsForProperty; + } - if (encryptionSettingsForProperty == null) + public void SetRequestHeaders(RequestOptions requestOptions) + { + requestOptions.AddRequestHeaders = (headers) => { - return null; - } + headers.Add(IsClientEncryptedHeader, bool.TrueString); + headers.Add(IntendedCollectionHeader, this.ContainerRidValue); + }; + } - return encryptionSettingsForProperty; + private EncryptionSettings(string containerRidValue) + { + this.ContainerRidValue = containerRidValue; + this.encryptionSettingsDictByPropertyName = new Dictionary(); + this.PropertiesToEncrypt = this.encryptionSettingsDictByPropertyName.Keys; } - private async Task FetchEncryptionSettingForPropertyAsync( - string propertyName, - CancellationToken cancellationToken) + private static EncryptionType GetEncryptionTypeForProperty(ClientEncryptionIncludedPath clientEncryptionIncludedPath) { - ClientEncryptionPolicy clientEncryptionPolicy = await this.EncryptionProcessor.EncryptionCosmosClient.GetClientEncryptionPolicyAsync( - this.EncryptionProcessor.Container, - cancellationToken: cancellationToken, - shouldForceRefresh: false); + return clientEncryptionIncludedPath.EncryptionType switch + { + CosmosEncryptionType.Deterministic => EncryptionType.Deterministic, + CosmosEncryptionType.Randomized => EncryptionType.Randomized, + _ => throw new ArgumentException($"Invalid encryption type {clientEncryptionIncludedPath.EncryptionType}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "), + }; + } + + private static async Task InitializeEncryptionSettingsAsync(EncryptionContainer encryptionContainer, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + ContainerResponse containerResponse = await encryptionContainer.ReadContainerAsync(); + + Debug.Assert(containerResponse.StatusCode == HttpStatusCode.OK, "ReadContainerAsync request has failed as part of InitializeEncryptionSettingsAsync operation. "); + Debug.Assert(containerResponse.Resource != null, "Null resource received in ContainerResponse as part of InitializeEncryptionSettingsAsync operation. "); + + // set the Database Rid. + string databaseRidValue = containerResponse.Resource.SelfLink.Split('/').ElementAt(1); + + // set the Container Rid. + string containerRidValue = containerResponse.Resource.SelfLink.Split('/').ElementAt(3); + + // set the ClientEncryptionPolicy for the Settings. + ClientEncryptionPolicy clientEncryptionPolicy = containerResponse.Resource.ClientEncryptionPolicy; + + EncryptionSettings encryptionSettings = new EncryptionSettings(containerRidValue); if (clientEncryptionPolicy != null) { - foreach (ClientEncryptionIncludedPath propertyToEncrypt in clientEncryptionPolicy.IncludedPaths) + if (clientEncryptionPolicy.PolicyFormatVersion > Constants.SupportedClientEncryptionPolicyFormatVersion) { - if (string.Equals(propertyToEncrypt.Path.Substring(1), propertyName)) - { - EncryptionType encryptionType = this.GetEncryptionTypeForProperty(propertyToEncrypt); - - return new EncryptionSettingForProperty( - propertyToEncrypt.ClientEncryptionKeyId, - encryptionType, - this.EncryptionProcessor); - } + throw new InvalidOperationException("This version of Microsoft.Azure.Cosmos.Encryption cannot be used with this container." + + " Please upgrade to the latest version of the same. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } - } - return null; - } + // for each of the unique keys in the policy Add it in /Update the cache. + foreach (string clientEncryptionKeyId in clientEncryptionPolicy.IncludedPaths.Select(x => x.ClientEncryptionKeyId).Distinct()) + { + await encryptionContainer.EncryptionCosmosClient.GetClientEncryptionKeyPropertiesAsync( + clientEncryptionKeyId: clientEncryptionKeyId, + encryptionContainer: encryptionContainer, + databaseRid: databaseRidValue, + cancellationToken: cancellationToken); + } - internal EncryptionType GetEncryptionTypeForProperty(ClientEncryptionIncludedPath clientEncryptionIncludedPath) - { - switch (clientEncryptionIncludedPath.EncryptionType) - { - case CosmosEncryptionType.Deterministic: - return EncryptionType.Deterministic; - case CosmosEncryptionType.Randomized: - return EncryptionType.Randomized; - case CosmosEncryptionType.Plaintext: - return EncryptionType.Plaintext; - default: - throw new ArgumentException($"Invalid encryption type {clientEncryptionIncludedPath.EncryptionType}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); + // update the property level setting. + foreach (ClientEncryptionIncludedPath propertyToEncrypt in clientEncryptionPolicy.IncludedPaths) + { + EncryptionType encryptionType = GetEncryptionTypeForProperty(propertyToEncrypt); + + EncryptionSettingForProperty encryptionSettingsForProperty = new EncryptionSettingForProperty( + propertyToEncrypt.ClientEncryptionKeyId, + encryptionType, + encryptionContainer, + databaseRidValue); + + string propertyName = propertyToEncrypt.Path.Substring(1); + + encryptionSettings.SetEncryptionSettingForProperty( + propertyName, + encryptionSettingsForProperty); + } } + + return encryptionSettings; } - internal void SetEncryptionSettingForProperty( + private void SetEncryptionSettingForProperty( string propertyName, EncryptionSettingForProperty encryptionSettingsForProperty) { - this.EncryptionSettingCacheByPropertyName.Set(propertyName, encryptionSettingsForProperty); + this.encryptionSettingsDictByPropertyName[propertyName] = encryptionSettingsForProperty; } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionTransactionalBatch.cs b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionTransactionalBatch.cs index e4839b7078..f8186fa5c2 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/EncryptionTransactionalBatch.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/EncryptionTransactionalBatch.cs @@ -7,6 +7,8 @@ namespace Microsoft.Azure.Cosmos.Encryption using System; using System.Collections.Generic; using System.IO; + using System.Linq; + using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos; @@ -15,16 +17,16 @@ namespace Microsoft.Azure.Cosmos.Encryption internal sealed class EncryptionTransactionalBatch : TransactionalBatch { private readonly CosmosSerializer cosmosSerializer; - private readonly EncryptionProcessor encryptionProcessor; + private readonly EncryptionContainer encryptionContainer; private TransactionalBatch transactionalBatch; public EncryptionTransactionalBatch( TransactionalBatch transactionalBatch, - EncryptionProcessor encryptionProcessor, + EncryptionContainer encryptionContainer, CosmosSerializer cosmosSerializer) { this.transactionalBatch = transactionalBatch ?? throw new ArgumentNullException(nameof(transactionalBatch)); - this.encryptionProcessor = encryptionProcessor ?? throw new ArgumentNullException(nameof(encryptionProcessor)); + this.encryptionContainer = encryptionContainer ?? throw new ArgumentNullException(nameof(encryptionContainer)); this.cosmosSerializer = cosmosSerializer ?? throw new ArgumentNullException(nameof(cosmosSerializer)); } @@ -45,10 +47,22 @@ public override TransactionalBatch CreateItemStream( CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("EncryptItemStream")) { - streamPayload = this.encryptionProcessor.EncryptAsync( - streamPayload, - diagnosticsContext, - cancellationToken: default).Result; + EncryptionSettings encryptionSettings = this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: default) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + + if (encryptionSettings.PropertiesToEncrypt.Any()) + { + streamPayload = EncryptionProcessor.EncryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken: default) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + } } this.transactionalBatch = this.transactionalBatch.CreateItemStream( @@ -100,10 +114,22 @@ public override TransactionalBatch ReplaceItemStream( CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("EncryptItemStream")) { - streamPayload = this.encryptionProcessor.EncryptAsync( - streamPayload, - diagnosticsContext, - default).Result; + EncryptionSettings encryptionSettings = this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: default) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + + if (encryptionSettings.PropertiesToEncrypt.Any()) + { + streamPayload = EncryptionProcessor.EncryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + default) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + } } this.transactionalBatch = this.transactionalBatch.ReplaceItemStream( @@ -131,10 +157,22 @@ public override TransactionalBatch UpsertItemStream( CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions); using (diagnosticsContext.CreateScope("EncryptItemStream")) { - streamPayload = this.encryptionProcessor.EncryptAsync( - streamPayload, - diagnosticsContext, - cancellationToken: default).Result; + EncryptionSettings encryptionSettings = this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: default) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + + if (encryptionSettings.PropertiesToEncrypt.Any()) + { + streamPayload = EncryptionProcessor.EncryptAsync( + streamPayload, + encryptionSettings, + diagnosticsContext, + cancellationToken: default) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); + } } this.transactionalBatch = this.transactionalBatch.UpsertItemStream( @@ -147,15 +185,7 @@ public override TransactionalBatch UpsertItemStream( public override async Task ExecuteAsync( CancellationToken cancellationToken = default) { - CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null); - using (diagnosticsContext.CreateScope("TransactionalBatch.ExecuteAsync")) - { - TransactionalBatchResponse response = await this.transactionalBatch.ExecuteAsync(cancellationToken); - return await this.DecryptTransactionalBatchResponseAsync( - response, - diagnosticsContext, - cancellationToken); - } + return await this.ExecuteAsync(requestOptions: null, cancellationToken: cancellationToken); } public override async Task ExecuteAsync( @@ -165,19 +195,71 @@ public override async Task ExecuteAsync( CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(options: null); using (diagnosticsContext.CreateScope("TransactionalBatch.ExecuteAsync.WithRequestOptions")) { - TransactionalBatchResponse response = await this.transactionalBatch.ExecuteAsync(requestOptions, cancellationToken); + TransactionalBatchResponse response = null; + + EncryptionSettings encryptionSettings = await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return await this.transactionalBatch.ExecuteAsync(requestOptions, cancellationToken); + } + else + { + TransactionalBatchRequestOptions clonedRequestOptions; + if (requestOptions != null) + { + clonedRequestOptions = (TransactionalBatchRequestOptions)requestOptions.ShallowCopy(); + } + else + { + clonedRequestOptions = new TransactionalBatchRequestOptions(); + } + + encryptionSettings.SetRequestHeaders(clonedRequestOptions); + response = await this.transactionalBatch.ExecuteAsync(clonedRequestOptions, cancellationToken); + } + + // FIXME this should check for BadRequest StatusCode too, requires a service fix to return 400 instead of -1 which is currently returned. + if (string.Equals(response.Headers.Get(Constants.SubStatusHeader), Constants.IncorrectContainerRidSubStatus)) + { + await this.encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync( + obsoleteEncryptionSettings: encryptionSettings, + cancellationToken: cancellationToken); + + throw new CosmosException( + "Operation has failed due to a possible mismatch in Client Encryption Policy configured on the container. Please refer to https://aka.ms/CosmosClientEncryption for more details. " + response.ErrorMessage, + HttpStatusCode.BadRequest, + int.Parse(Constants.IncorrectContainerRidSubStatus), + response.Headers.ActivityId, + response.Headers.RequestCharge); + } + return await this.DecryptTransactionalBatchResponseAsync( response, + encryptionSettings, diagnosticsContext, cancellationToken); } } + public override TransactionalBatch PatchItem( + string id, + IReadOnlyList patchOperations, + TransactionalBatchPatchItemRequestOptions requestOptions = null) + { + throw new NotImplementedException(); + } + private async Task DecryptTransactionalBatchResponseAsync( TransactionalBatchResponse response, + EncryptionSettings encryptionSettings, CosmosDiagnosticsContext diagnosticsContext, CancellationToken cancellationToken) { + if (!encryptionSettings.PropertiesToEncrypt.Any()) + { + return response; + } + List decryptedTransactionalBatchOperationResults = new List(); for (int index = 0; index < response.Count; index++) @@ -186,8 +268,9 @@ private async Task DecryptTransactionalBatchResponse if (response.IsSuccessStatusCode && result.ResourceStream != null) { - Stream decryptedStream = await this.encryptionProcessor.DecryptAsync( + Stream decryptedStream = await EncryptionProcessor.DecryptAsync( result.ResourceStream, + encryptionSettings, diagnosticsContext, cancellationToken); @@ -202,13 +285,5 @@ private async Task DecryptTransactionalBatchResponse response, this.cosmosSerializer); } - - public override TransactionalBatch PatchItem( - string id, - IReadOnlyList patchOperations, - TransactionalBatchPatchItemRequestOptions requestOptions = null) - { - throw new NotImplementedException(); - } } } diff --git a/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj b/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj index c2b23e8dfb..8eef61e304 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj +++ b/Microsoft.Azure.Cosmos.Encryption/src/Microsoft.Azure.Cosmos.Encryption.csproj @@ -24,8 +24,15 @@ + + + + + + + + - diff --git a/Microsoft.Azure.Cosmos.Encryption/src/QueryDefinitionExtensions.cs b/Microsoft.Azure.Cosmos.Encryption/src/QueryDefinitionExtensions.cs index d7ffb1c761..48a2588df7 100644 --- a/Microsoft.Azure.Cosmos.Encryption/src/QueryDefinitionExtensions.cs +++ b/Microsoft.Azure.Cosmos.Encryption/src/QueryDefinitionExtensions.cs @@ -9,7 +9,6 @@ namespace Microsoft.Azure.Cosmos.Encryption using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Encryption.Cryptography; - using Newtonsoft.Json.Linq; /// /// This class provides extension methods for . @@ -75,16 +74,10 @@ public static async Task AddParameterAsync( if (queryDefinition is EncryptionQueryDefinition encryptionQueryDefinition) { EncryptionContainer encryptionContainer = (EncryptionContainer)encryptionQueryDefinition.Container; - Stream valueStream = encryptionContainer.CosmosSerializer.ToStream(value); - - // not really required, but will have things setup for subsequent queries or operations on this Container if the Container was never init - // or if this was the first operation carried out on this container. - await encryptionContainer.EncryptionProcessor.InitEncryptionSettingsIfNotInitializedAsync(cancellationToken); // get the path's encryption setting. - EncryptionSettingForProperty settingsForProperty = await encryptionContainer.EncryptionProcessor.EncryptionSettings.GetEncryptionSettingForPropertyAsync( - path.Substring(1), - cancellationToken); + EncryptionSettings encryptionSettings = await encryptionContainer.GetOrUpdateEncryptionSettingsFromCacheAsync(obsoleteEncryptionSettings: null, cancellationToken: cancellationToken); + EncryptionSettingForProperty settingsForProperty = encryptionSettings.GetEncryptionSettingForProperty(path.Substring(1)); if (settingsForProperty == null) { @@ -98,22 +91,9 @@ public static async Task AddParameterAsync( throw new ArgumentException($"Unsupported argument with Path: {path} for query. For executing queries on encrypted path requires the use of deterministic encryption type. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); } - AeadAes256CbcHmac256EncryptionAlgorithm aeadAes256CbcHmac256EncryptionAlgorithm = await settingsForProperty.BuildEncryptionAlgorithmForSettingAsync(cancellationToken: cancellationToken); - - JToken propertyValueToEncrypt = EncryptionProcessor.BaseSerializer.FromStream(valueStream); - (EncryptionProcessor.TypeMarker typeMarker, byte[] serializedData) = EncryptionProcessor.Serialize(propertyValueToEncrypt); - - byte[] cipherText = aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt(serializedData); - - if (cipherText == null) - { - throw new InvalidOperationException($"{nameof(AddParameterAsync)} returned null cipherText from {nameof(aeadAes256CbcHmac256EncryptionAlgorithm.Encrypt)}. Please refer to https://aka.ms/CosmosClientEncryption for more details. "); - } - - byte[] cipherTextWithTypeMarker = new byte[cipherText.Length + 1]; - cipherTextWithTypeMarker[0] = (byte)typeMarker; - Buffer.BlockCopy(cipherText, 0, cipherTextWithTypeMarker, 1, cipherText.Length); - queryDefinitionwithEncryptedValues.WithParameter(name, cipherTextWithTypeMarker); + Stream valueStream = encryptionContainer.CosmosSerializer.ToStream(value); + Stream encryptedValueStream = await EncryptionProcessor.EncryptValueStreamAsync(valueStream, settingsForProperty, cancellationToken); + queryDefinitionwithEncryptedValues.WithParameterStream(name, encryptedValueStream); return queryDefinitionwithEncryptedValues; } diff --git a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeCustomEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeCustomEncryptionTests.cs index 00271d60f2..c1a097989e 100644 --- a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeCustomEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeCustomEncryptionTests.cs @@ -34,6 +34,8 @@ public class MdeCustomEncryptionTests private static DataEncryptionKeyProperties dekProperties; private static Container itemContainer; private static Container encryptionContainer; + private static Container itemContainerForChangeFeed; + private static Container encryptionContainerForChangeFeed; private static Container keyContainer; private static TestEncryptionKeyStoreProvider testKeyStoreProvider; private static CosmosDataEncryptionKeyProvider dekProvider; @@ -57,12 +59,14 @@ public static async Task ClassInitialize(TestContext context) MdeCustomEncryptionTests.database = await MdeCustomEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); MdeCustomEncryptionTests.keyContainer = await MdeCustomEncryptionTests.database.CreateContainerAsync(Guid.NewGuid().ToString(), "/id", 400); MdeCustomEncryptionTests.itemContainer = await MdeCustomEncryptionTests.database.CreateContainerAsync(Guid.NewGuid().ToString(), "/PK", 400); + MdeCustomEncryptionTests.itemContainerForChangeFeed = await MdeCustomEncryptionTests.database.CreateContainerAsync(Guid.NewGuid().ToString(), "/PK", 400); MdeCustomEncryptionTests.testKeyStoreProvider = new TestEncryptionKeyStoreProvider(); await LegacyClassInitializeAsync(); MdeCustomEncryptionTests.encryptor = new TestEncryptor(MdeCustomEncryptionTests.dekProvider); MdeCustomEncryptionTests.encryptionContainer = MdeCustomEncryptionTests.itemContainer.WithEncryptor(encryptor); + MdeCustomEncryptionTests.encryptionContainerForChangeFeed = MdeCustomEncryptionTests.itemContainerForChangeFeed.WithEncryptor(encryptor); await MdeCustomEncryptionTests.dekProvider.InitializeAsync(MdeCustomEncryptionTests.database, MdeCustomEncryptionTests.keyContainer.Id); MdeCustomEncryptionTests.dekProperties = await MdeCustomEncryptionTests.CreateDekAsync(MdeCustomEncryptionTests.dekProvider, MdeCustomEncryptionTests.dekId); @@ -445,14 +449,26 @@ public async Task EncryptionChangeFeedDecryptionSuccessful() string dek2 = "dek2ForChangeFeed"; await MdeCustomEncryptionTests.CreateDekAsync(MdeCustomEncryptionTests.dekProvider, dek2); - TestDoc testDoc1 = await MdeCustomEncryptionTests.CreateItemAsync(MdeCustomEncryptionTests.encryptionContainer, MdeCustomEncryptionTests.dekId, TestDoc.PathsToEncrypt); - TestDoc testDoc2 = await MdeCustomEncryptionTests.CreateItemAsync(MdeCustomEncryptionTests.encryptionContainer, dek2, TestDoc.PathsToEncrypt); + TestDoc testDoc1 = await MdeCustomEncryptionTests.CreateItemAsync(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, MdeCustomEncryptionTests.dekId, TestDoc.PathsToEncrypt); + TestDoc testDoc2 = await MdeCustomEncryptionTests.CreateItemAsync(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, dek2, TestDoc.PathsToEncrypt); // change feed iterator - await this.ValidateChangeFeedIteratorResponse(MdeCustomEncryptionTests.encryptionContainer, testDoc1, testDoc2); + await this.ValidateChangeFeedIteratorResponse(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); // change feed processor - await this.ValidateChangeFeedProcessorResponse(MdeCustomEncryptionTests.encryptionContainer, testDoc1, testDoc2); + await this.ValidateChangeFeedProcessorResponse(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor with feed handler + await this.ValidateChangeFeedProcessorWithFeedHandlerResponse(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor with manual checkpoint + await this.ValidateChangeFeedProcessorWithManualCheckpointResponse(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor with feed stream handler + await this.ValidateChangeFeedProcessorWithFeedStreamHandlerResponse(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor manual checkpoint with feed stream handler + await this.ValidateChangeFeedProcessorStreamWithManualCheckpointResponse(MdeCustomEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); } [TestMethod] @@ -512,7 +528,7 @@ public async Task EncryptionHandleDecryptionFailure() }) .WithInstanceName("dummy") .WithLeaseContainer(leaseContainer) - .WithStartTime(DateTime.UtcNow.AddMinutes(-5)) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) .Build(); await cfp.StartAsync(); @@ -1126,19 +1142,16 @@ private async Task ValidateChangeFeedIteratorResponse( ChangeFeedStartFrom.Beginning(), ChangeFeedMode.Incremental); - List changeFeedReturnedDocs = new List(); while (changeIterator.HasMoreResults) { try { FeedResponse testDocs = await changeIterator.ReadNextAsync(); - for (int index = 0; index < testDocs.Count; index++) - { - if (testDocs.Resource.ElementAt(index).Id.Equals(testDoc1.Id) || testDocs.Resource.ElementAt(index).Id.Equals(testDoc2.Id)) - { - changeFeedReturnedDocs.Add(testDocs.Resource.ElementAt(index)); - } - } + + Assert.AreEqual(testDocs.Count, 2); + + VerifyExpectedDocResponse(testDoc1, testDocs.Resource.ElementAt(0)); + VerifyExpectedDocResponse(testDoc2, testDocs.Resource.ElementAt(1)); } catch (CosmosException ex) { @@ -1146,52 +1159,260 @@ private async Task ValidateChangeFeedIteratorResponse( break; } } + } + + private async Task ValidateChangeFeedProcessorResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeCustomEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + List changeFeedReturnedDocs = new List(); + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilder( + "testCFP", + (IReadOnlyCollection changes, CancellationToken cancellationToken) => + { + changeFeedReturnedDocs.AddRange(changes); + processedDocCount += changes.Count(); + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); Assert.AreEqual(changeFeedReturnedDocs.Count, 2); VerifyExpectedDocResponse(testDoc1, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 2]); VerifyExpectedDocResponse(testDoc2, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 1]); + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } } - private async Task ValidateChangeFeedProcessorResponse( + private async Task ValidateChangeFeedProcessorWithFeedHandlerResponse( Container container, TestDoc testDoc1, TestDoc testDoc2) { - Container leaseContainer = await MdeCustomEncryptionTests.database.CreateContainerIfNotExistsAsync( + Database leaseDatabase = await MdeCustomEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; List changeFeedReturnedDocs = new List(); ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilder( - "testCFP", - (IReadOnlyCollection changes, CancellationToken cancellationToken) - => + "testCFPWithFeedHandler", + ( + ChangeFeedProcessorContext context, + IReadOnlyCollection changes, + CancellationToken cancellationToken) => { changeFeedReturnedDocs.AddRange(changes); + processedDocCount += changes.Count(); + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + return Task.CompletedTask; }) .WithInstanceName("random") .WithLeaseContainer(leaseContainer) - .WithStartTime(DateTime.UtcNow.AddMinutes(-5)) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) .Build(); await cfp.StartAsync(); - await Task.Delay(2000); + bool isStartOk = allDocsProcessed.WaitOne(60000); await cfp.StopAsync(); - Assert.IsTrue(changeFeedReturnedDocs.Count >= 2); + Assert.AreEqual(changeFeedReturnedDocs.Count, 2); + + VerifyExpectedDocResponse(testDoc1, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 2]); + VerifyExpectedDocResponse(testDoc2, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 1]); - foreach (TestDoc testDoc in changeFeedReturnedDocs) + if (leaseDatabase != null) { - if (testDoc.Id.Equals(testDoc1.Id)) + using (await leaseDatabase.DeleteStreamAsync()) { } + } + } + + private async Task ValidateChangeFeedProcessorWithManualCheckpointResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeCustomEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + List changeFeedReturnedDocs = new List(); + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + "testCFPWithManualCheckpoint", + ( + ChangeFeedProcessorContext context, + IReadOnlyCollection changes, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => { - VerifyExpectedDocResponse(testDoc1, testDoc); - } - else if (testDoc.Id.Equals(testDoc2.Id)) + changeFeedReturnedDocs.AddRange(changes); + processedDocCount += changes.Count(); + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); + + Assert.AreEqual(changeFeedReturnedDocs.Count, 2); + + VerifyExpectedDocResponse(testDoc1, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 2]); + VerifyExpectedDocResponse(testDoc2, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 1]); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } + } + + private async Task ValidateChangeFeedProcessorWithFeedStreamHandlerResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeCustomEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilder( + "testCFPWithFeedStreamHandler", + ( + ChangeFeedProcessorContext context, + Stream changes, + CancellationToken cancellationToken) => { - VerifyExpectedDocResponse(testDoc2, testDoc); - } + string changeFeed = string.Empty; + using (StreamReader streamReader = new StreamReader(changes)) + { + changeFeed = streamReader.ReadToEnd(); + } + + if (changeFeed.Contains(testDoc1.Id)) + { + processedDocCount++; + } + + if (changeFeed.Contains(testDoc2.Id)) + { + processedDocCount++; + } + + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } + } + + private async Task ValidateChangeFeedProcessorStreamWithManualCheckpointResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeCustomEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + "testCFPStreamWithManualCheckpoint", + ( + ChangeFeedProcessorContext context, + Stream changes, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => + { + string changeFeed = string.Empty; + using (StreamReader streamReader = new StreamReader(changes)) + { + changeFeed = streamReader.ReadToEnd(); + } + + if (changeFeed.Contains(testDoc1.Id)) + { + processedDocCount++; + } + + if (changeFeed.Contains(testDoc2.Id)) + { + processedDocCount++; + } + + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } } } diff --git a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs index f9f6b1cf7d..bdd54dcfaf 100644 --- a/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs +++ b/Microsoft.Azure.Cosmos.Encryption/tests/EmulatorTests/MdeEncryptionTests.cs @@ -19,19 +19,20 @@ namespace Microsoft.Azure.Cosmos.Encryption.EmulatorTests using Microsoft.Data.Encryption.Cryptography; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; - + using Newtonsoft.Json.Linq; using EncryptionKeyWrapMetadata = Cosmos.EncryptionKeyWrapMetadata; [TestClass] public class MdeEncryptionTests { - private static readonly EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata("key1", "tempmetadata1"); - private static readonly EncryptionKeyWrapMetadata metadata2 = new EncryptionKeyWrapMetadata("key2", "tempmetadata2"); + private static EncryptionKeyWrapMetadata metadata1; + private static EncryptionKeyWrapMetadata metadata2; private static CosmosClient client; private static CosmosClient encryptionCosmosClient; private static Database database; private static Container encryptionContainer; + private static Container encryptionContainerForChangeFeed; private static TestEncryptionKeyStoreProvider testEncryptionKeyStoreProvider; [ClassInitialize] @@ -43,6 +44,10 @@ public static async Task ClassInitialize(TestContext context) { DataEncryptionKeyCacheTimeToLive = null }; + + metadata1 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, "key1", "tempmetadata1"); + metadata2 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, "key2", "tempmetadata2"); + MdeEncryptionTests.encryptionCosmosClient = MdeEncryptionTests.client.WithEncryption(testEncryptionKeyStoreProvider); MdeEncryptionTests.database = await MdeEncryptionTests.encryptionCosmosClient.CreateDatabaseAsync(Guid.NewGuid().ToString()); @@ -54,6 +59,13 @@ await MdeEncryptionTests.CreateClientEncryptionKeyAsync( "key2", metadata2); + + EncryptionKeyWrapMetadata revokedKekmetadata = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, "revokedKek", "revokedKek-metadata"); + await database.CreateClientEncryptionKeyAsync( + "keywithRevokedKek", + DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, + revokedKekmetadata); + Collection paths = new Collection() { new ClientEncryptionIncludedPath() @@ -67,7 +79,7 @@ await MdeEncryptionTests.CreateClientEncryptionKeyAsync( new ClientEncryptionIncludedPath() { Path = "/Sensitive_ArrayFormat", - ClientEncryptionKeyId = "key2", + ClientEncryptionKeyId = "keywithRevokedKek", EncryptionType = "Deterministic", EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", }, @@ -143,6 +155,14 @@ await MdeEncryptionTests.CreateClientEncryptionKeyAsync( EncryptionType = "Deterministic", EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_ObjectArrayType", + ClientEncryptionKeyId = "key2", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, }; @@ -150,9 +170,13 @@ await MdeEncryptionTests.CreateClientEncryptionKeyAsync( ContainerProperties containerProperties = new ContainerProperties(Guid.NewGuid().ToString(), "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + ContainerProperties containerPropertiesForChangeFeed = new ContainerProperties(Guid.NewGuid().ToString(), "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; encryptionContainer = await database.CreateContainerAsync(containerProperties, 400); - await encryptionContainer.InitializeEncryptionAsync(); + await encryptionContainer.InitializeEncryptionAsync(); + + encryptionContainerForChangeFeed = await database.CreateContainerAsync(containerPropertiesForChangeFeed, 400); + await encryptionContainerForChangeFeed.InitializeEncryptionAsync(); } private static async Task CreateClientEncryptionKeyAsync(string cekId, Cosmos.EncryptionKeyWrapMetadata encryptionKeyWrapMetadata) @@ -233,17 +257,19 @@ public async Task EncryptionBulkCrud() public async Task EncryptionCreateClientEncryptionKey() { string cekId = "anotherCek"; - EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(cekId, "testmetadata1"); + + EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, cekId, "testmetadata1"); + ClientEncryptionKeyProperties clientEncryptionKeyProperties = await MdeEncryptionTests.CreateClientEncryptionKeyAsync( cekId, metadata1); Assert.AreEqual( - new EncryptionKeyWrapMetadata(name: cekId, value: metadata1.Value), + new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, name: cekId, value: metadata1.Value), clientEncryptionKeyProperties.EncryptionKeyWrapMetadata); // creating another key with same id should fail - metadata1 = new EncryptionKeyWrapMetadata(cekId, "testmetadata2"); + metadata1 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, cekId, "testmetadata2"); try { @@ -264,22 +290,22 @@ await MdeEncryptionTests.CreateClientEncryptionKeyAsync( public async Task EncryptionRewrapClientEncryptionKey() { string cekId = "rewrapkeytest"; - EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(cekId, "testmetadata1"); + EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, cekId, "testmetadata1"); ClientEncryptionKeyProperties clientEncryptionKeyProperties = await MdeEncryptionTests.CreateClientEncryptionKeyAsync( cekId, metadata1); Assert.AreEqual( - new EncryptionKeyWrapMetadata(name: cekId, value: metadata1.Value), + new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, name: cekId, value: metadata1.Value), clientEncryptionKeyProperties.EncryptionKeyWrapMetadata); - EncryptionKeyWrapMetadata updatedMetaData = new EncryptionKeyWrapMetadata(cekId, metadata1 + "updatedmetadata"); + EncryptionKeyWrapMetadata updatedMetaData = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, cekId, metadata1 + "updatedmetadata"); clientEncryptionKeyProperties = await MdeEncryptionTests.RewarpClientEncryptionKeyAsync( cekId, updatedMetaData); Assert.AreEqual( - new EncryptionKeyWrapMetadata(name: cekId, value: updatedMetaData.Value), + new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, name: cekId, value: updatedMetaData.Value), clientEncryptionKeyProperties.EncryptionKeyWrapMetadata); } @@ -304,16 +330,48 @@ await MdeEncryptionTests.encryptionContainer.CreateItemAsync( [TestMethod] public async Task EncryptionCreateItemWithNullProperty() { + CosmosClient clientWithNoCaching = TestCommon.CreateCosmosClient(builder => builder + .Build()); + + TestEncryptionKeyStoreProvider testEncryptionKeyStoreProvider = new TestEncryptionKeyStoreProvider + { + DataEncryptionKeyCacheTimeToLive = TimeSpan.Zero + }; + + CosmosClient encryptionCosmosClient = clientWithNoCaching.WithEncryption(testEncryptionKeyStoreProvider); + Database database = encryptionCosmosClient.GetDatabase(MdeEncryptionTests.database.Id); + + Container encryptionContainer = database.GetContainer(MdeEncryptionTests.encryptionContainer.Id); + TestDoc testDoc = TestDoc.Create(); testDoc.Sensitive_ArrayFormat = null; testDoc.Sensitive_StringFormat = null; - ItemResponse createResponse = await MdeEncryptionTests.encryptionContainer.CreateItemAsync( + + ItemResponse createResponse = await encryptionContainer.CreateItemAsync( + testDoc, + new PartitionKey(testDoc.PK)); + + Assert.AreEqual(HttpStatusCode.Created, createResponse.StatusCode); + + VerifyExpectedDocResponse(testDoc, createResponse.Resource); + + // no access to key. + testEncryptionKeyStoreProvider.RevokeAccessSet = true; + + testDoc = TestDoc.Create(); + + testDoc.Sensitive_ArrayFormat = null; + testDoc.Sensitive_StringFormat = null; + + createResponse = await encryptionContainer.CreateItemAsync( testDoc, new PartitionKey(testDoc.PK)); Assert.AreEqual(HttpStatusCode.Created, createResponse.StatusCode); VerifyExpectedDocResponse(testDoc, createResponse.Resource); + + testEncryptionKeyStoreProvider.RevokeAccessSet = false; } @@ -339,7 +397,7 @@ public async Task EncryptionResourceTokenAuthRestricted() try { string cekId = "testingcekID"; - EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(cekId, "testmetadata1"); + EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, cekId, "testmetadata1"); ClientEncryptionKeyResponse clientEncrytionKeyResponse = await databaseForRestrictedUser.CreateClientEncryptionKeyAsync( cekId, @@ -354,7 +412,7 @@ public async Task EncryptionResourceTokenAuthRestricted() try { string cekId = "testingcekID"; - EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(cekId, "testmetadata1" + "updated"); + EncryptionKeyWrapMetadata metadata1 = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, cekId, "testmetadata1" + "updated"); ClientEncryptionKeyResponse clientEncrytionKeyResponse = await databaseForRestrictedUser.RewrapClientEncryptionKeyAsync( cekId, @@ -449,10 +507,11 @@ await MdeEncryptionTests.ValidateQueryResultsAsync( expectedDoc.Sensitive_DateFormat = new DateTime(); expectedDoc.Sensitive_StringFormat = null; expectedDoc.Sensitive_IntArray = null; + expectedDoc.Sensitive_ObjectArrayType = null; await MdeEncryptionTests.ValidateQueryResultsAsync( MdeEncryptionTests.encryptionContainer, - "SELECT c.id, c.PK, c.NonSensitive FROM c", + "SELECT c.id, c.PK, c.NonSensitive, c.NonSensitiveInt FROM c", expectedDoc); } @@ -462,18 +521,27 @@ public async Task QueryOnEncryptedProperties() TestDoc testDoc1 = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); // string/int + string[] arrayofStringValues = new string[] { testDoc1.Sensitive_StringFormat, "randomValue" }; + QueryDefinition withEncryptedParameter = MdeEncryptionTests.encryptionContainer.CreateQueryDefinition( - "SELECT * FROM c where c.Sensitive_StringFormat = @Sensitive_StringFormat AND c.Sensitive_IntFormat = @Sensitive_IntFormat"); + "SELECT * FROM c where array_contains(@Sensitive_StringFormat, c.Sensitive_StringFormat) " + + "AND c.Sensitive_IntArray = @Sensitive_IntArray " + + "AND c.Sensitive_NestedObjectFormatL1 = @Sensitive_NestedObjectFormatL1"); await withEncryptedParameter.AddParameterAsync( "@Sensitive_StringFormat", - testDoc1.Sensitive_StringFormat, + arrayofStringValues, "/Sensitive_StringFormat"); - + await withEncryptedParameter.AddParameterAsync( - "@Sensitive_IntFormat", - testDoc1.Sensitive_IntFormat, - "/Sensitive_IntFormat"); + "@Sensitive_IntArray", + testDoc1.Sensitive_IntArray, + "/Sensitive_IntArray"); + + await withEncryptedParameter.AddParameterAsync( + "@Sensitive_NestedObjectFormatL1", + testDoc1.Sensitive_NestedObjectFormatL1, + "/Sensitive_NestedObjectFormatL1"); TestDoc expectedDoc = new TestDoc(testDoc1); await MdeEncryptionTests.ValidateQueryResultsAsync( @@ -637,14 +705,26 @@ public async Task EncryptionTransactionalBatchConflictResponse() [TestMethod] public async Task EncryptionChangeFeedDecryptionSuccessful() { - TestDoc testDoc1 = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); - TestDoc testDoc2 = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); + TestDoc testDoc1 = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainerForChangeFeed); + TestDoc testDoc2 = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainerForChangeFeed); // change feed iterator - await this.ValidateChangeFeedIteratorResponse(MdeEncryptionTests.encryptionContainer, testDoc1, testDoc2); + await this.ValidateChangeFeedIteratorResponse(MdeEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); // change feed processor - await this.ValidateChangeFeedProcessorResponse(MdeEncryptionTests.encryptionContainer, testDoc1, testDoc2); + await this.ValidateChangeFeedProcessorResponse(MdeEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor with feed handler + await this.ValidateChangeFeedProcessorWithFeedHandlerResponse(MdeEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor with manual checkpoint + await this.ValidateChangeFeedProcessorWithManualCheckpointResponse(MdeEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor with feed stream handler + await this.ValidateChangeFeedProcessorWithFeedStreamHandlerResponse(MdeEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); + + // change feed processor manual checkpoint with feed stream handler + await this.ValidateChangeFeedProcessorStreamWithManualCheckpointResponse(MdeEncryptionTests.encryptionContainerForChangeFeed, testDoc1, testDoc2); } [TestMethod] @@ -755,15 +835,12 @@ public async Task EncryptionRestrictedProperties() ContainerProperties containerProperties = new ContainerProperties(Guid.NewGuid().ToString(), "/PK") { ClientEncryptionPolicy = clientEncryptionPolicyPk }; - Container encryptionContainer = await database.CreateContainerAsync(containerProperties, 400); - await encryptionContainer.InitializeEncryptionAsync(); - try { - await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainer); - Assert.Fail("Expected item creation with PK specified to be encrypted to fail."); + Container encryptionContainer = await database.CreateContainerAsync(containerProperties, 400); + Assert.Fail("CreateContainerAsync operation with PK specified to be encrypted should have failed. "); } - catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.BadRequest) + catch (ArgumentException) { } @@ -785,16 +862,543 @@ public async Task EncryptionRestrictedProperties() }; - Collection pathsWithDups = new Collection { pathdup1 , pathdup2 }; + Collection pathsWithDups = new Collection { pathdup1 , pathdup2 }; + + try + { + ClientEncryptionPolicy clientEncryptionPolicyWithDupPaths = new ClientEncryptionPolicy(pathsWithDups); + Assert.Fail("Client Encryption Policy Creation Should have Failed."); + } + catch (ArgumentException) + { + } + } + + [TestMethod] + public async Task EncryptionValidatePolicyRefreshPostContainerDeleteWithBulk() + { + Collection paths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_IntArray", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_ArrayFormat", + ClientEncryptionKeyId = "key2", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_NestedObjectFormatL1", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + + ContainerProperties containerProperties = new ContainerProperties(Guid.NewGuid().ToString(), "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + Container encryptionContainerToDelete = await database.CreateContainerAsync(containerProperties, 400); + await encryptionContainerToDelete.InitializeEncryptionAsync(); + + // FIXME Set WithBulkExecution to true post SDK/Backend fix. + CosmosClient otherClient = TestCommon.CreateCosmosClient(builder => builder + .WithBulkExecution(false) + .Build()); + + CosmosClient otherEncryptionClient = otherClient.WithEncryption(new TestEncryptionKeyStoreProvider()); + Database otherDatabase = otherEncryptionClient.GetDatabase(MdeEncryptionTests.database.Id); + + Container otherEncryptionContainer = otherDatabase.GetContainer(encryptionContainerToDelete.Id); + + await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer); + + // Client 1 Deletes the Container referenced in Client 2 and Recreate with different policy + using (await database.GetContainer(encryptionContainerToDelete.Id).DeleteContainerStreamAsync()) + { } + + paths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_StringFormat", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_DateFormat", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_BoolFormat", + ClientEncryptionKeyId = "key2", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + + containerProperties = new ContainerProperties(encryptionContainerToDelete.Id, "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + ContainerResponse containerResponse = await database.CreateContainerAsync(containerProperties, 400); + + TestDoc docToReplace = await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete); + + docToReplace.Sensitive_StringFormat = "docTobeReplace"; + + TestDoc docToUpsert = await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete); + docToUpsert.Sensitive_StringFormat = "docTobeUpserted"; + + List tasks = new List() + { + MdeEncryptionTests.MdeUpsertItemAsync(otherEncryptionContainer, docToUpsert, HttpStatusCode.OK), + MdeEncryptionTests.MdeReplaceItemAsync(otherEncryptionContainer, docToReplace), + MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer), + }; + + await Task.WhenAll(tasks); + + tasks = new List() + { + MdeEncryptionTests.VerifyItemByReadAsync(encryptionContainerToDelete, docToReplace), + MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete), + MdeEncryptionTests.VerifyItemByReadAsync(encryptionContainerToDelete, docToUpsert), + }; + + await Task.WhenAll(tasks); + + // validate if the right policy was used, by reading them all back. + FeedIterator queryResponseIterator = otherEncryptionContainer.GetItemQueryIterator("select * from c"); + + while (queryResponseIterator.HasMoreResults) + { + FeedResponse readDocs = await queryResponseIterator.ReadNextAsync(); + } + } + + [TestMethod] + public async Task EncryptionValidatePolicyRefreshPostContainerDeleteTransactionBatch() + { + Collection paths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_StringFormat", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_DateFormat", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + + ContainerProperties containerProperties = new ContainerProperties(Guid.NewGuid().ToString(), "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + Container encryptionContainerToDelete = await database.CreateContainerAsync(containerProperties, 400); + await encryptionContainerToDelete.InitializeEncryptionAsync(); + + CosmosClient otherClient = TestCommon.CreateCosmosClient(builder => builder + .Build()); + + CosmosClient otherEncryptionClient = otherClient.WithEncryption(new TestEncryptionKeyStoreProvider()); + Database otherDatabase = otherEncryptionClient.GetDatabase(MdeEncryptionTests.database.Id); + + Container otherEncryptionContainer = otherDatabase.GetContainer(encryptionContainerToDelete.Id); + + await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer); + + // Client 1 Deletes the Container referenced in Client 2 and Recreate with different policy + using (await database.GetContainer(encryptionContainerToDelete.Id).DeleteContainerStreamAsync()) + { } + + paths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_IntArray", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_NestedObjectFormatL1", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + + containerProperties = new ContainerProperties(encryptionContainerToDelete.Id, "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + ContainerResponse containerResponse = await database.CreateContainerAsync(containerProperties, 400); + + TestDoc testDoc = await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete); + + string partitionKey = "thePK"; + + TestDoc doc1ToCreate = TestDoc.Create(partitionKey); + TestDoc doc2ToCreate = TestDoc.Create(partitionKey); + + // check w.r.t to Batch if we are able to fail and update the policy. + TransactionalBatchResponse batchResponse = null; + try + { + batchResponse = await otherEncryptionContainer.CreateTransactionalBatch(new Cosmos.PartitionKey(partitionKey)) + .CreateItem(doc1ToCreate) + .CreateItemStream(doc2ToCreate.ToStream()) + .ReadItem(doc1ToCreate.Id) + .DeleteItem(doc2ToCreate.Id) + .ExecuteAsync(); + + Assert.Fail("CreateTransactionalBatch should have failed. "); + } + catch(CosmosException ex) + { + Assert.AreEqual("Operation has failed due to a possible mismatch in Client Encryption Policy configured on the container. Please refer to https://aka.ms/CosmosClientEncryption for more details. ", ex.Message); + } + + // the previous failure would have updated the policy in the cache. + batchResponse = await otherEncryptionContainer.CreateTransactionalBatch(new Cosmos.PartitionKey(partitionKey)) + .CreateItem(doc1ToCreate) + .CreateItemStream(doc2ToCreate.ToStream()) + .ReadItem(doc1ToCreate.Id) + .DeleteItem(doc2ToCreate.Id) + .ExecuteAsync(); + + Assert.AreEqual(HttpStatusCode.OK, batchResponse.StatusCode); + + TransactionalBatchOperationResult doc1 = batchResponse.GetOperationResultAtIndex(0); + VerifyExpectedDocResponse(doc1ToCreate, doc1.Resource); + + TransactionalBatchOperationResult doc2 = batchResponse.GetOperationResultAtIndex(1); + VerifyExpectedDocResponse(doc2ToCreate, doc2.Resource); + } + + [TestMethod] + public async Task EncryptionValidatePolicyRefreshPostContainerDeleteQuery() + { + Collection paths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_StringFormat", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_NestedObjectFormatL1", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + + ContainerProperties containerProperties = new ContainerProperties(Guid.NewGuid().ToString(), "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + Container encryptionContainerToDelete = await database.CreateContainerAsync(containerProperties, 400); + await encryptionContainerToDelete.InitializeEncryptionAsync(); + + CosmosClient otherClient = TestCommon.CreateCosmosClient(builder => builder + .Build()); + + CosmosClient otherEncryptionClient = otherClient.WithEncryption(new TestEncryptionKeyStoreProvider()); + Database otherDatabase = otherEncryptionClient.GetDatabase(MdeEncryptionTests.database.Id); + + Container otherEncryptionContainer = otherDatabase.GetContainer(encryptionContainerToDelete.Id); + + await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer); + + // Client 1 Deletes the Container referenced in Client 2 and Recreate with different policy + using (await database.GetContainer(encryptionContainerToDelete.Id).DeleteContainerStreamAsync()) + { } + + paths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_IntArray", + ClientEncryptionKeyId = "key1", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_ArrayFormat", + ClientEncryptionKeyId = "key2", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + clientEncryptionPolicy = new ClientEncryptionPolicy(paths); + + containerProperties = new ContainerProperties(encryptionContainerToDelete.Id, "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + ContainerResponse containerResponse = await database.CreateContainerAsync(containerProperties, 400); + + TestDoc testDoc = await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete); + + // check w.r.t to query if we are able to fail and update the policy + try + { + await MdeEncryptionTests.ValidateQueryResultsAsync( + otherEncryptionContainer, + "SELECT * FROM c", + testDoc); + + Assert.Fail("ValidateQueryResultAsync should have failed. "); + } + catch(CosmosException ex) + { + if (ex.SubStatusCode != 1024) + { + Assert.Fail("Query should have failed. "); + } + + Assert.IsTrue(ex.Message.Contains("Operation has failed due to a possible mismatch in Client Encryption Policy configured on the container. Please refer to https://aka.ms/CosmosClientEncryption for more details. ")); + } + + // previous failure would have updated the policy in the cache. + await MdeEncryptionTests.ValidateQueryResultsAsync( + otherEncryptionContainer, + "SELECT * FROM c", + testDoc); + } + + [TestMethod] + public async Task EncryptionValidatePolicyRefreshPostDatabaseDelete() + { + CosmosClient mainClient = TestCommon.CreateCosmosClient(builder => builder + .Build()); + + TestEncryptionKeyStoreProvider testEncryptionKeyStoreProvider = new TestEncryptionKeyStoreProvider + { + DataEncryptionKeyCacheTimeToLive = TimeSpan.FromMinutes(30), + }; + + EncryptionKeyWrapMetadata keyWrapMetadata = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider.ProviderName, "myCek", "mymetadata1"); + CosmosClient encryptionCosmosClient = mainClient.WithEncryption(testEncryptionKeyStoreProvider); + Database mainDatabase = await encryptionCosmosClient.CreateDatabaseAsync("databaseToBeDeleted"); + + ClientEncryptionKeyResponse clientEncrytionKeyResponse = await mainDatabase.CreateClientEncryptionKeyAsync( + keyWrapMetadata.Name, + DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, + keyWrapMetadata); + + Collection originalPaths = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_StringFormat", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_ArrayFormat", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_NestedObjectFormatL1", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + ClientEncryptionPolicy clientEncryptionPolicy = new ClientEncryptionPolicy(originalPaths); + + ContainerProperties containerProperties = new ContainerProperties("containerToBeDeleted", "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + Container encryptionContainerToDelete = await mainDatabase.CreateContainerAsync(containerProperties, 400); + await encryptionContainerToDelete.InitializeEncryptionAsync(); + + await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete); + + CosmosClient otherClient1 = TestCommon.CreateCosmosClient(builder => builder + .Build()); + + TestEncryptionKeyStoreProvider testEncryptionKeyStoreProvider2 = new TestEncryptionKeyStoreProvider + { + DataEncryptionKeyCacheTimeToLive = TimeSpan.Zero, + }; + + CosmosClient otherEncryptionClient = otherClient1.WithEncryption(testEncryptionKeyStoreProvider2); + Database otherDatabase = otherEncryptionClient.GetDatabase(mainDatabase.Id); + + Container otherEncryptionContainer = otherDatabase.GetContainer(encryptionContainerToDelete.Id); + + await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer); + + // Client 1 Deletes the Database and Container referenced in Client 2 and Recreate with different policy + // delete database and recreate with same key name + await mainClient.GetDatabase("databaseToBeDeleted").DeleteStreamAsync(); + + mainDatabase = await encryptionCosmosClient.CreateDatabaseAsync("databaseToBeDeleted"); + + keyWrapMetadata = new EncryptionKeyWrapMetadata(testEncryptionKeyStoreProvider2.ProviderName, "myCek", "mymetadata2"); + clientEncrytionKeyResponse = await mainDatabase.CreateClientEncryptionKeyAsync( + keyWrapMetadata.Name, + DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, + keyWrapMetadata); + + using (await mainDatabase.GetContainer(encryptionContainerToDelete.Id).DeleteContainerStreamAsync()) + { } + + Collection newModifiedPath = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_IntArray", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_DateFormat", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + + new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_BoolFormat", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }, + }; + + clientEncryptionPolicy = new ClientEncryptionPolicy(newModifiedPath); + + containerProperties = new ContainerProperties(encryptionContainerToDelete.Id, "/PK") { ClientEncryptionPolicy = clientEncryptionPolicy }; + + ContainerResponse containerResponse = await mainDatabase.CreateContainerAsync(containerProperties, 400); + //encryptionContainerToDelete = containerResponse; + + TestDoc testDoc = await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainerToDelete); + + await MdeEncryptionTests.VerifyItemByReadAsync(otherEncryptionContainer, testDoc); + + // create new container in other client. + // The test basically validates if the new key created is referenced, Since the other client would have had the old key cached. + // and here we would not hit the incorrect container rid issue. + ClientEncryptionIncludedPath newModifiedPath2 = new ClientEncryptionIncludedPath() + { + Path = "/Sensitive_StringFormat", + ClientEncryptionKeyId = "myCek", + EncryptionType = "Deterministic", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + }; + + Container otherEncryptionContainer2 = await otherDatabase.DefineContainer("otherContainer2", "/PK") + .WithClientEncryptionPolicy() + .WithIncludedPath(newModifiedPath2) + .Attach() + .CreateAsync(throughput: 1000); + + + // create an item + TestDoc newdoc = await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer2); + + CosmosClient otherClient2 = TestCommon.CreateCosmosClient(builder => builder + .WithBulkExecution(true) + .Build()); + + TestEncryptionKeyStoreProvider testEncryptionKeyStoreProvider3 = new TestEncryptionKeyStoreProvider + { + DataEncryptionKeyCacheTimeToLive = TimeSpan.FromMinutes(30), + }; + + CosmosClient otherEncryptionClient2 = otherClient2.WithEncryption(testEncryptionKeyStoreProvider3); + Database otherDatabase2 = otherEncryptionClient2.GetDatabase(mainDatabase.Id); + + Container otherEncryptionContainer3 = otherDatabase2.GetContainer(otherEncryptionContainer2.Id); + await MdeEncryptionTests.VerifyItemByReadAsync(otherEncryptionContainer3, newdoc); + + // validate from other client that we indeed are using the key with metadata 2 + Container otherEncryptionContainerFromClient2 = otherDatabase2.GetContainer(encryptionContainerToDelete.Id); + await MdeEncryptionTests.VerifyItemByReadAsync(otherEncryptionContainerFromClient2, testDoc); + + // previous refrenced container. + await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer); + + List tasks = new List() + { + // readback item and create item from other Client. + MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer), + MdeEncryptionTests.VerifyItemByReadAsync(otherEncryptionContainer, testDoc), + }; + + await Task.WhenAll(tasks); + + testDoc = await MdeEncryptionTests.MdeCreateItemAsync(otherEncryptionContainer); + + // to be sure if it was indeed encrypted with the new key. + await MdeEncryptionTests.VerifyItemByReadAsync(encryptionContainerToDelete, testDoc); + + + // validate if the right policy was used, by reading them all back. + FeedIterator queryResponseIterator = encryptionContainerToDelete.GetItemQueryIterator("select * from c"); - try + while (queryResponseIterator.HasMoreResults) { - ClientEncryptionPolicy clientEncryptionPolicyWithDupPaths = new ClientEncryptionPolicy(pathsWithDups); - Assert.Fail("Client Encryption Policy Creation Should have Failed."); + await queryResponseIterator.ReadNextAsync(); } - catch (ArgumentException) + + queryResponseIterator = otherEncryptionContainer.GetItemQueryIterator("select * from c"); + + while (queryResponseIterator.HasMoreResults) { + await queryResponseIterator.ReadNextAsync(); } + + await mainClient.GetDatabase("databaseToBeDeleted").DeleteStreamAsync(); } [TestMethod] @@ -814,13 +1418,6 @@ public async Task VerifyKekRevokeHandling() // Once a Dek gets cached and the Kek is revoked, calls to unwrap/wrap keys would fail since KEK is revoked. // The Dek should be rewrapped if the KEK is revoked. // When an access to KeyVault fails, the Dek is fetched from the backend(force refresh to update the stale DEK) and cache is updated. - EncryptionKeyWrapMetadata revokedKekmetadata = new EncryptionKeyWrapMetadata("revokedKek", "revokedKek-metadata"); - - await database.CreateClientEncryptionKeyAsync( - "keywithRevokedKek", - DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, - revokedKekmetadata); - ClientEncryptionIncludedPath pathwithRevokedKek = new ClientEncryptionIncludedPath() { Path = "/Sensitive_NestedObjectFormatL1", @@ -913,13 +1510,17 @@ public async Task CreateAndDeleteDatabaseWithoutKeys() await encryptionContainer.InitializeEncryptionAsync(); TestDoc testDoc = TestDoc.Create(); - ItemResponse createResponse = await encryptionContainer.CreateItemAsync( testDoc, new PartitionKey(testDoc.PK)); Assert.AreEqual(HttpStatusCode.Created, createResponse.StatusCode); VerifyExpectedDocResponse(testDoc, createResponse.Resource); + await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainer); + await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainer); + await MdeEncryptionTests.MdeCreateItemAsync(encryptionContainer); + + await database.DeleteStreamAsync(); } @@ -967,6 +1568,122 @@ public async Task EncryptionRudItem() await MdeEncryptionTests.MdeDeleteItemAsync(MdeEncryptionTests.encryptionContainer, replacedDoc); } + [TestMethod] + public async Task EncryptionPatchItem() + { + TestDoc docPostPatching = await MdeEncryptionTests.MdeCreateItemAsync(MdeEncryptionTests.encryptionContainer); + docPostPatching.NonSensitive = Guid.NewGuid().ToString(); + docPostPatching.NonSensitiveInt++; + docPostPatching.Sensitive_StringFormat = Guid.NewGuid().ToString(); + docPostPatching.Sensitive_DateFormat = new DateTime(2020, 02, 02); + docPostPatching.Sensitive_DecimalFormat = 11.11m; + docPostPatching.Sensitive_IntArray[1] = 1; + docPostPatching.Sensitive_IntMultiDimArray[1, 0] = 7; + docPostPatching.Sensitive_IntFormat = 2020; + docPostPatching.Sensitive_BoolFormat = false; + docPostPatching.Sensitive_FloatFormat = 2020.20f; + + // Maximum 10 operations at a time (current limit) + List patchOperations = new List + { + PatchOperation.Increment("/NonSensitiveInt", 1), + PatchOperation.Replace("/NonSensitive", docPostPatching.NonSensitive), + PatchOperation.Replace("/Sensitive_StringFormat", docPostPatching.Sensitive_StringFormat), + PatchOperation.Replace("/Sensitive_DateFormat", docPostPatching.Sensitive_DateFormat), + PatchOperation.Replace("/Sensitive_DecimalFormat", docPostPatching.Sensitive_DecimalFormat), + PatchOperation.Set("/Sensitive_IntArray/1", docPostPatching.Sensitive_IntArray[1]), + PatchOperation.Set("/Sensitive_IntMultiDimArray/1/0", docPostPatching.Sensitive_IntMultiDimArray[1,0]), + PatchOperation.Replace("/Sensitive_IntFormat", docPostPatching.Sensitive_IntFormat), + PatchOperation.Replace("/Sensitive_BoolFormat", docPostPatching.Sensitive_BoolFormat), + PatchOperation.Replace("/Sensitive_FloatFormat", docPostPatching.Sensitive_FloatFormat), + }; + + await MdeEncryptionTests.MdePatchItemAsync( + MdeEncryptionTests.encryptionContainer, + patchOperations, + docPostPatching, + HttpStatusCode.OK); + + docPostPatching.Sensitive_ArrayFormat = new TestDoc.Sensitive_ArrayData[] + { + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 1111, + Sensitive_ArrayDecimalFormat = 1111.11m + }, + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2222, + Sensitive_ArrayDecimalFormat = 2222.22m + } + }; + + docPostPatching.Sensitive_ArrayMultiTypes.SetValue( + new TestDoc.Sensitive_ArrayMultiType() + { + Sensitive_NestedObjectFormatL0 = new TestDoc.Sensitive_NestedObjectL0() + { + Sensitive_IntFormatL0 = 123, + Sensitive_DecimalFormatL0 = 123.1m, + }, + Sensitive_StringArrayMultiType = new string[1] { "sensitivedata" }, + Sensitive_ArrayMultiTypeDecimalFormat = 123.2m, + Sensitive_IntArrayMultiType = new int[2] { 1, 2 } + }, + 0, + 1); + + docPostPatching.Sensitive_NestedObjectFormatL1 = new TestDoc.Sensitive_NestedObjectL1() + { + Sensitive_IntArrayL1 = new int[2] { 999, 100 }, + Sensitive_IntFormatL1 = 1999, + Sensitive_DecimalFormatL1 = 1999.1m, + Sensitive_ArrayFormatL1 = new TestDoc.Sensitive_ArrayData[] + { + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 0, + Sensitive_ArrayDecimalFormat = 0.1m + }, + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 1, + Sensitive_ArrayDecimalFormat = 2.1m + }, + new TestDoc.Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2, + Sensitive_ArrayDecimalFormat = 3.1m + } + } + }; + + patchOperations.Clear(); + patchOperations.Add(PatchOperation.Add("/Sensitive_ArrayFormat/1", docPostPatching.Sensitive_ArrayFormat[1])); + patchOperations.Add(PatchOperation.Replace("/Sensitive_ArrayMultiTypes/0/1", docPostPatching.Sensitive_ArrayMultiTypes[0, 1])); + patchOperations.Add(PatchOperation.Remove("/Sensitive_NestedObjectFormatL1/Sensitive_NestedObjectFormatL2")); + patchOperations.Add(PatchOperation.Set("/Sensitive_NestedObjectFormatL1/Sensitive_ArrayFormatL1/0", docPostPatching.Sensitive_NestedObjectFormatL1.Sensitive_ArrayFormatL1[0])); + + await MdeEncryptionTests.MdePatchItemAsync( + MdeEncryptionTests.encryptionContainer, + patchOperations, + docPostPatching, + HttpStatusCode.OK); + + patchOperations.Add(PatchOperation.Increment("/Sensitive_IntFormat", 1)); + try + { + await MdeEncryptionTests.encryptionContainer.PatchItemAsync( + docPostPatching.Id, + new PartitionKey(docPostPatching.PK), + patchOperations); + } + catch(InvalidOperationException ex) + { + Assert.AreEqual("Increment patch operation is not allowed for encrypted path '/Sensitive_IntFormat'.", ex.Message); + } + } + [TestMethod] public async Task EncryptionTransactionalBatchWithCustomSerializer() { @@ -1158,19 +1875,15 @@ private async Task ValidateChangeFeedIteratorResponse( ChangeFeedStartFrom.Beginning(), ChangeFeedMode.Incremental); - List changeFeedReturnedDocs = new List(); while (changeIterator.HasMoreResults) { try { FeedResponse testDocs = await changeIterator.ReadNextAsync(); - for (int index = 0; index < testDocs.Count; index++) - { - if (testDocs.Resource.ElementAt(index).Id.Equals(testDoc1.Id) || testDocs.Resource.ElementAt(index).Id.Equals(testDoc2.Id)) - { - changeFeedReturnedDocs.Add(testDocs.Resource.ElementAt(index)); - } - } + Assert.AreEqual(testDocs.Count, 2); + + VerifyExpectedDocResponse(testDoc1, testDocs.Resource.ElementAt(0)); + VerifyExpectedDocResponse(testDoc2, testDocs.Resource.ElementAt(1)); } catch (CosmosException ex) { @@ -1178,15 +1891,54 @@ private async Task ValidateChangeFeedIteratorResponse( break; } } + } + + private async Task ValidateChangeFeedProcessorResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + List changeFeedReturnedDocs = new List(); + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilder( + "testCFP", + (IReadOnlyCollection changes, CancellationToken cancellationToken) => + { + changeFeedReturnedDocs.AddRange(changes); + processedDocCount += changes.Count(); + if(processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); Assert.AreEqual(changeFeedReturnedDocs.Count, 2); VerifyExpectedDocResponse(testDoc1, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 2]); VerifyExpectedDocResponse(testDoc2, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 1]); + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } } - private async Task ValidateChangeFeedProcessorResponse( + private async Task ValidateChangeFeedProcessorWithFeedHandlerResponse( Container container, TestDoc testDoc1, TestDoc testDoc2) @@ -1194,28 +1946,206 @@ private async Task ValidateChangeFeedProcessorResponse( Database leaseDatabase = await MdeEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; List changeFeedReturnedDocs = new List(); ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilder( - "testCFP", - (IReadOnlyCollection changes, CancellationToken cancellationToken) => + "testCFPWithFeedHandler", + ( + ChangeFeedProcessorContext context, + IReadOnlyCollection changes, + CancellationToken cancellationToken) => + { + changeFeedReturnedDocs.AddRange(changes); + processedDocCount += changes.Count(); + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); + + Assert.AreEqual(changeFeedReturnedDocs.Count, 2); + + VerifyExpectedDocResponse(testDoc1, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 2]); + VerifyExpectedDocResponse(testDoc2, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 1]); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } + } + + private async Task ValidateChangeFeedProcessorWithManualCheckpointResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + List changeFeedReturnedDocs = new List(); + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + "testCFPWithManualCheckpoint", + ( + ChangeFeedProcessorContext context, + IReadOnlyCollection changes, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => { changeFeedReturnedDocs.AddRange(changes); + processedDocCount += changes.Count(); + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + return Task.CompletedTask; }) .WithInstanceName("random") .WithLeaseContainer(leaseContainer) - .WithStartTime(DateTime.UtcNow.AddMinutes(-5)) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) .Build(); await cfp.StartAsync(); - await Task.Delay(2000); + bool isStartOk = allDocsProcessed.WaitOne(60000); await cfp.StopAsync(); - Assert.IsTrue(changeFeedReturnedDocs.Count >= 2); + Assert.AreEqual(changeFeedReturnedDocs.Count, 2); VerifyExpectedDocResponse(testDoc1, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 2]); VerifyExpectedDocResponse(testDoc2, changeFeedReturnedDocs[changeFeedReturnedDocs.Count - 1]); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } + } + + private async Task ValidateChangeFeedProcessorWithFeedStreamHandlerResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilder( + "testCFPWithFeedStreamHandler", + ( + ChangeFeedProcessorContext context, + Stream changes, + CancellationToken cancellationToken) => + { + string changeFeed = string.Empty; + using (StreamReader streamReader = new StreamReader(changes)) + { + changeFeed = streamReader.ReadToEnd(); + } + + if (changeFeed.Contains(testDoc1.Id)) + { + processedDocCount++; + } + + if (changeFeed.Contains(testDoc2.Id)) + { + processedDocCount++; + } + + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } + } + + private async Task ValidateChangeFeedProcessorStreamWithManualCheckpointResponse( + Container container, + TestDoc testDoc1, + TestDoc testDoc2) + { + Database leaseDatabase = await MdeEncryptionTests.client.CreateDatabaseAsync(Guid.NewGuid().ToString()); + Container leaseContainer = await leaseDatabase.CreateContainerIfNotExistsAsync( + new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + int processedDocCount = 0; + + ChangeFeedProcessor cfp = container.GetChangeFeedProcessorBuilderWithManualCheckpoint( + "testCFPStreamWithManualCheckpoint", + ( + ChangeFeedProcessorContext context, + Stream changes, + Func> tryCheckpointAsync, + CancellationToken cancellationToken) => + { + string changeFeed = string.Empty; + using (StreamReader streamReader = new StreamReader(changes)) + { + changeFeed = streamReader.ReadToEnd(); + } + + if (changeFeed.Contains(testDoc1.Id)) + { + processedDocCount++; + } + + if (changeFeed.Contains(testDoc2.Id)) + { + processedDocCount++; + } + + if (processedDocCount == 2) + { + allDocsProcessed.Set(); + } + + return Task.CompletedTask; + }) + .WithInstanceName("random") + .WithLeaseContainer(leaseContainer) + .WithStartTime(DateTime.MinValue.ToUniversalTime()) + .Build(); + + await cfp.StartAsync(); + bool isStartOk = allDocsProcessed.WaitOne(60000); + await cfp.StopAsync(); + + if (leaseDatabase != null) + { + using (await leaseDatabase.DeleteStreamAsync()) { } + } } // One of query or queryDefinition is to be passed in non-null @@ -1309,6 +2239,23 @@ private static async Task> MdeReplaceItemAsync( VerifyExpectedDocResponse(testDoc, replaceResponse.Resource); return replaceResponse; } + + private static async Task> MdePatchItemAsync( + Container container, + List patchOperations, + TestDoc expectedTestDoc, + HttpStatusCode expectedStatusCode) + { + ItemResponse patchResponse = await container.PatchItemAsync( + expectedTestDoc.Id, + new PartitionKey(expectedTestDoc.PK), + patchOperations); + + Assert.AreEqual(expectedStatusCode, patchResponse.StatusCode); + VerifyExpectedDocResponse(expectedTestDoc, patchResponse.Resource); + return patchResponse; + } + private static ItemRequestOptions MdeGetRequestOptions( string ifMatchEtag = null) { @@ -1366,6 +2313,20 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif } } + if(expectedDoc.Sensitive_ObjectArrayType != null) + { + TestDoc.Sensitive_ArrayData expectedValue = expectedDoc.Sensitive_ObjectArrayType[0] is JObject jObjectValue + ? jObjectValue.ToObject() + : (TestDoc.Sensitive_ArrayData)expectedDoc.Sensitive_ObjectArrayType[0]; + jObjectValue = (JObject)verifyDoc.Sensitive_ObjectArrayType[0]; + TestDoc.Sensitive_ArrayData test = jObjectValue.ToObject(); + + Assert.AreEqual(expectedValue.Sensitive_ArrayDecimalFormat, test.Sensitive_ArrayDecimalFormat); + Assert.AreEqual(expectedValue.Sensitive_ArrayIntFormat, test.Sensitive_ArrayIntFormat); + + Assert.AreEqual(expectedDoc.Sensitive_ObjectArrayType[1], verifyDoc.Sensitive_ObjectArrayType[1]); + } + if (expectedDoc.Sensitive_ArrayMultiTypes != null) { for (int i = 0; i < expectedDoc.Sensitive_ArrayMultiTypes.GetLength(0); i++) @@ -1390,8 +2351,8 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif for (int k = 0; k < expectedDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_IntArrayMultiType.Length; k++) { - Assert.AreEqual(expectedDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_StringArrayMultiType[k], - verifyDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_StringArrayMultiType[k]); + Assert.AreEqual(expectedDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_IntArrayMultiType[k], + verifyDoc.Sensitive_ArrayMultiTypes[i,j].Sensitive_IntArrayMultiType[k]); } } } @@ -1400,41 +2361,49 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif if (expectedDoc.Sensitive_NestedObjectFormatL1 != null) { Assert.AreEqual(expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_IntFormatL1, verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_IntFormatL1); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3); + if (expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2 == null) + { + Assert.IsNull(verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2); + } + else + { + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_IntFormatL2); + + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_IntFormatL3); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_DecimalFormatL3); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayIntFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayFormatL3[0].Sensitive_ArrayDecimalFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayDecimalFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_ArrayIntFormat); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_IntFormatL0); - Assert.AreEqual( - expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0, - verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0); + Assert.AreEqual( + expectedDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0, + verifyDoc.Sensitive_NestedObjectFormatL1.Sensitive_NestedObjectFormatL2.Sensitive_NestedObjectFormatL3.Sensitive_ArrayWithObjectFormat[0].Sensitive_NestedObjectFormatL0.Sensitive_DecimalFormatL0); + } } else { @@ -1447,6 +2416,7 @@ private static void VerifyExpectedDocResponse(TestDoc expectedDoc, TestDoc verif Assert.AreEqual(expectedDoc.Sensitive_FloatFormat, verifyDoc.Sensitive_FloatFormat); Assert.AreEqual(expectedDoc.Sensitive_BoolFormat, verifyDoc.Sensitive_BoolFormat); Assert.AreEqual(expectedDoc.NonSensitive, verifyDoc.NonSensitive); + Assert.AreEqual(expectedDoc.NonSensitiveInt, verifyDoc.NonSensitiveInt); } public class TestDoc @@ -1459,6 +2429,8 @@ public class TestDoc public string NonSensitive { get; set; } + public int NonSensitiveInt { get; set; } + public string Sensitive_StringFormat { get; set; } public DateTime Sensitive_DateFormat { get; set; } @@ -1479,6 +2451,8 @@ public class TestDoc public Sensitive_ArrayMultiType[,] Sensitive_ArrayMultiTypes { get; set; } + public object[] Sensitive_ObjectArrayType { get; set; } + public Sensitive_NestedObjectL1 Sensitive_NestedObjectFormatL1 { get; set; } public TestDoc() @@ -1542,6 +2516,7 @@ public TestDoc(TestDoc other) this.Id = other.Id; this.PK = other.PK; this.NonSensitive = other.NonSensitive; + this.NonSensitiveInt = other.NonSensitiveInt; this.Sensitive_StringFormat = other.Sensitive_StringFormat; this.Sensitive_DateFormat = other.Sensitive_DateFormat; this.Sensitive_DecimalFormat = other.Sensitive_DecimalFormat; @@ -1550,6 +2525,7 @@ public TestDoc(TestDoc other) this.Sensitive_FloatFormat = other.Sensitive_FloatFormat; this.Sensitive_ArrayFormat = other.Sensitive_ArrayFormat; this.Sensitive_IntArray = other.Sensitive_IntArray; + this.Sensitive_ObjectArrayType = other.Sensitive_ObjectArrayType; this.Sensitive_NestedObjectFormatL1 = other.Sensitive_NestedObjectFormatL1; this.Sensitive_ArrayMultiTypes = other.Sensitive_ArrayMultiTypes; } @@ -1560,6 +2536,7 @@ public override bool Equals(object obj) && this.Id == doc.Id && this.PK == doc.PK && this.NonSensitive == doc.NonSensitive + && this.NonSensitiveInt == doc.NonSensitiveInt && this.Sensitive_StringFormat == doc.Sensitive_StringFormat && this.Sensitive_DateFormat == doc.Sensitive_DateFormat && this.Sensitive_DecimalFormat == doc.Sensitive_DecimalFormat @@ -1568,6 +2545,7 @@ public override bool Equals(object obj) && this.Sensitive_BoolFormat == doc.Sensitive_BoolFormat && this.Sensitive_FloatFormat == doc.Sensitive_FloatFormat && this.Sensitive_IntArray == doc.Sensitive_IntArray + && this.Sensitive_ObjectArrayType == doc.Sensitive_ObjectArrayType && this.Sensitive_NestedObjectFormatL1 != doc.Sensitive_NestedObjectFormatL1 && this.Sensitive_ArrayMultiTypes != doc.Sensitive_ArrayMultiTypes; } @@ -1578,6 +2556,7 @@ public bool EqualsExceptEncryptedProperty(object obj) && this.Id == doc.Id && this.PK == doc.PK && this.NonSensitive == doc.NonSensitive + && this.NonSensitiveInt == doc.NonSensitiveInt && this.Sensitive_StringFormat != doc.Sensitive_StringFormat; } public override int GetHashCode() @@ -1586,10 +2565,12 @@ public override int GetHashCode() hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Id); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.PK); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.NonSensitive); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.NonSensitiveInt); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_StringFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_DateFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_DecimalFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_IntFormat); + hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_ObjectArrayType); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_ArrayFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_BoolFormat); hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(this.Sensitive_FloatFormat); @@ -1605,11 +2586,21 @@ public static TestDoc Create(string partitionKey = null) Id = Guid.NewGuid().ToString(), PK = partitionKey ?? Guid.NewGuid().ToString(), NonSensitive = Guid.NewGuid().ToString(), + NonSensitiveInt = 10, Sensitive_StringFormat = Guid.NewGuid().ToString(), Sensitive_DateFormat = new DateTime(1987, 12, 25), Sensitive_DecimalFormat = 472.3108m, Sensitive_IntArray = new int[2] { 999, 1000 }, Sensitive_IntMultiDimArray = new[,] { { 1, 2 }, { 2, 3 }, { 4, 5 } }, + Sensitive_ObjectArrayType = new object[2] + { + (Sensitive_ArrayData)new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 18273, + Sensitive_ArrayDecimalFormat = 1234.11m + }, + (Int64)9823 + }, Sensitive_IntFormat = 1965, Sensitive_BoolFormat = true, Sensitive_FloatFormat = 8923.124f, @@ -1624,7 +2615,7 @@ public static TestDoc Create(string partitionKey = null) Sensitive_ArrayMultiTypes = new Sensitive_ArrayMultiType[,] { { - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -1635,7 +2626,7 @@ public static TestDoc Create(string partitionKey = null) Sensitive_ArrayMultiTypeDecimalFormat = 10.2m, Sensitive_IntArrayMultiType = new int[2] { 999, 1000 } }, - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -1648,7 +2639,7 @@ public static TestDoc Create(string partitionKey = null) } }, { - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -1659,7 +2650,7 @@ public static TestDoc Create(string partitionKey = null) Sensitive_ArrayMultiTypeDecimalFormat = 9876.2m, Sensitive_IntArrayMultiType = new int[2] { 1, 2 } }, - new Sensitive_ArrayMultiType() + new Sensitive_ArrayMultiType() { Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() { @@ -1678,55 +2669,55 @@ public static TestDoc Create(string partitionKey = null) Sensitive_IntFormatL1 = 1999, Sensitive_DecimalFormatL1 = 1999.1m, Sensitive_ArrayFormatL1 = new Sensitive_ArrayData[] - { - new Sensitive_ArrayData { - Sensitive_ArrayIntFormat = 1, - Sensitive_ArrayDecimalFormat = 2.1m + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 1, + Sensitive_ArrayDecimalFormat = 2.1m + }, + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2, + Sensitive_ArrayDecimalFormat = 3.1m + } }, - new Sensitive_ArrayData - { - Sensitive_ArrayIntFormat = 2, - Sensitive_ArrayDecimalFormat = 3.1m - } - }, Sensitive_NestedObjectFormatL2 = new Sensitive_NestedObjectL2() { Sensitive_IntFormatL2 = 2000, Sensitive_DecimalFormatL2 = 2000.1m, Sensitive_ArrayFormatL2 = new Sensitive_ArrayData[] - { - new Sensitive_ArrayData - { - Sensitive_ArrayIntFormat = 2, - Sensitive_ArrayDecimalFormat = 3.1m - } - }, + { + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 2, + Sensitive_ArrayDecimalFormat = 3.1m + } + }, Sensitive_NestedObjectFormatL3 = new Sensitive_NestedObjectL3() { Sensitive_IntFormatL3 = 3000, Sensitive_DecimalFormatL3 = 3000.1m, Sensitive_ArrayFormatL3 = new Sensitive_ArrayData[] - { - new Sensitive_ArrayData - { - Sensitive_ArrayIntFormat = 3, - Sensitive_ArrayDecimalFormat = 4.1m, - } - }, + { + new Sensitive_ArrayData + { + Sensitive_ArrayIntFormat = 3, + Sensitive_ArrayDecimalFormat = 4.1m, + } + }, Sensitive_ArrayWithObjectFormat = new Sensitive_ArrayDataWithObject[] - { - new Sensitive_ArrayDataWithObject - { - Sensitive_ArrayIntFormat = 4, - Sensitive_ArrayDecimalFormat = 5.1m, - Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() - { - Sensitive_IntFormatL0 = 888, - Sensitive_DecimalFormatL0 = 888.1m, - } - } - } + { + new Sensitive_ArrayDataWithObject + { + Sensitive_ArrayIntFormat = 4, + Sensitive_ArrayDecimalFormat = 5.1m, + Sensitive_NestedObjectFormatL0 = new Sensitive_NestedObjectL0() + { + Sensitive_IntFormatL0 = 888, + Sensitive_DecimalFormatL0 = 888.1m, + } + } + } } } } diff --git a/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Encryption.csproj b/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Encryption.csproj index ee327e0f99..e285cb1e41 100644 --- a/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Encryption.csproj +++ b/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Encryption.csproj @@ -6,38 +6,21 @@ Cosmos.Samples.Encryption Cosmos.Samples.Encryption latest - true + + - + PreserveNewest - - - - - - - - - - - - - - - - - $(DefineConstants);PREVIEW - - + diff --git a/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Program.cs b/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Program.cs index b0dbbb0b87..067f249c40 100644 --- a/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Program.cs +++ b/Microsoft.Azure.Cosmos.Samples/Usage/Encryption/Program.cs @@ -1,12 +1,15 @@ namespace Cosmos.Samples.Encryption { using System; - using System.Linq; + using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; + using Azure.Core; + using Azure.Identity; using Cosmos.Samples.Shared; using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.Encryption; using Microsoft.Data.Encryption.Cryptography; + using Microsoft.Data.Encryption.AzureKeyVaultProvider; using Microsoft.Extensions.Configuration; // ---------------------------------------------------------------------------------------------------------- @@ -23,17 +26,17 @@ public class Program { - private const string encrypteddatabaseId = "encryptedDb"; - private const string encryptedcontainerId = "encryptedData"; + private const string encryptedDatabaseId = "encryptedDb"; + private const string encryptedContainerId = "encryptedData"; private static CosmosClient client = null; + private static string MasterKeyUrl = null; + private static Container containerWithEncryption = null; //
-#pragma warning disable IDE0060 // Remove unused parameter - public static async Task Main(string[] args) -#pragma warning restore IDE0060 // Remove unused parameter + public static async Task Main(string[] _) { try { @@ -44,8 +47,20 @@ public static async Task Main(string[] args) .AddJsonFile("appSettings.json") .Build(); - Program.client = Program.CreateClientInstance(configuration); - await Program.AdminSetupAsync(client); + // Get the Akv Master Key Path. + MasterKeyUrl = configuration["MasterKeyUrl"]; + if (string.IsNullOrEmpty(MasterKeyUrl)) + { + throw new ArgumentNullException("Please specify a valid Azure Key Path in the appSettings.json"); + } + + // Get the Token Credential that is capable of providing an OAuth Token. + TokenCredential tokenCredential = GetTokenCredential(configuration); + AzureKeyVaultKeyStoreProvider azureKeyVaultKeyStoreProvider = new AzureKeyVaultKeyStoreProvider(tokenCredential); + + Program.client = Program.CreateClientInstance(configuration, azureKeyVaultKeyStoreProvider); + + await Program.AdminSetupAsync(client, azureKeyVaultKeyStoreProvider); await Program.RunDemoAsync(); } catch (CosmosException cre) @@ -66,7 +81,7 @@ public static async Task Main(string[] args) } //
- private static CosmosClient CreateClientInstance(IConfigurationRoot configuration) + private static CosmosClient CreateClientInstance(IConfigurationRoot configuration, AzureKeyVaultKeyStoreProvider azureKeyVaultKeyStoreProvider) { string endpoint = configuration["EndPointUrl"]; if (string.IsNullOrEmpty(endpoint)) @@ -83,19 +98,62 @@ private static CosmosClient CreateClientInstance(IConfigurationRoot configuratio CosmosClient encryptionCosmosClient = new CosmosClient(endpoint, authKey); // enable encryption support on the cosmos client. - return encryptionCosmosClient.WithEncryption(new TestEncryptionKeyStoreProvider()); + return encryptionCosmosClient.WithEncryption(azureKeyVaultKeyStoreProvider); + } + + private static X509Certificate2 GetCertificate(string clientCertThumbprint) + { + X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly); + X509Certificate2Collection certs = store.Certificates.Find(findType: X509FindType.FindByThumbprint, findValue: clientCertThumbprint, validOnly: false); + store.Close(); + + if (certs.Count == 0) + { + throw new ArgumentException("Certificate with thumbprint not found in CurrentUser certificate store"); + } + + return certs[0]; + } + + private static TokenCredential GetTokenCredential(IConfigurationRoot configuration) + { + // Application credentials for authentication with Azure Key Vault. + // This application must have keys/wrapKey and keys/unwrapKey permissions + // on the keys that will be used for encryption. + string clientId = configuration["ClientId"]; + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException("Please specify a valid ClientId in the appSettings.json"); + } + + // Get the Tenant ID + string tenantId = configuration["TenantId"]; + if (string.IsNullOrEmpty(tenantId)) + { + throw new ArgumentNullException("Please specify a valid TenantId in the appSettings.json"); + } + + // Certificate's public key must be at least 2048 bits. + string clientCertThumbprint = configuration["ClientCertThumbprint"]; + if (string.IsNullOrEmpty(clientCertThumbprint)) + { + throw new ArgumentNullException("Please specify a valid ClientCertThumbprint in the appSettings.json"); + } + + return new ClientCertificateCredential(tenantId, clientId, Program.GetCertificate(clientCertThumbprint)); } /// /// Administrative operations - create the database, container, and generate the necessary client encryption keys. /// These are initializations and are expected to be invoked only once - do not invoke these before every item request. /// - private static async Task AdminSetupAsync(CosmosClient client) + private static async Task AdminSetupAsync(CosmosClient client, AzureKeyVaultKeyStoreProvider azureKeyVaultKeyStoreProvider) { - Database database = await client.CreateDatabaseIfNotExistsAsync(Program.encrypteddatabaseId); + Database database = await client.CreateDatabaseIfNotExistsAsync(Program.encryptedDatabaseId); // Delete the existing container to prevent create item conflicts. - using (await database.GetContainer(Program.encryptedcontainerId).DeleteContainerStreamAsync()) + using (await database.GetContainer(Program.encryptedContainerId).DeleteContainerStreamAsync()) { } Console.WriteLine("The demo will create a 1000 RU/s container, press any key to continue."); @@ -105,12 +163,12 @@ private static async Task AdminSetupAsync(CosmosClient client) await database.CreateClientEncryptionKeyAsync( "key1", DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, - new EncryptionKeyWrapMetadata("key1", "metadata1")); + new EncryptionKeyWrapMetadata(azureKeyVaultKeyStoreProvider.ProviderName, "akvMasterKey", MasterKeyUrl)); await database.CreateClientEncryptionKeyAsync( "key2", DataEncryptionKeyAlgorithm.AEAD_AES_256_CBC_HMAC_SHA256, - new EncryptionKeyWrapMetadata("key2", "metadata2")); + new EncryptionKeyWrapMetadata(azureKeyVaultKeyStoreProvider.ProviderName, "akvMasterKey", MasterKeyUrl)); // Configure the required Paths to be Encrypted with appropriate settings. ClientEncryptionIncludedPath path1 = new ClientEncryptionIncludedPath() @@ -140,7 +198,7 @@ await database.CreateClientEncryptionKeyAsync( // Create a container with the appropriate partition key definition (we choose the "AccountNumber" property here) and throughput (we choose 1000 here). // Configure the Client Encryption Key Policy with required paths to be encrypted. - await database.DefineContainer(Program.encryptedcontainerId, "/AccountNumber") + await database.DefineContainer(Program.encryptedContainerId, "/AccountNumber") .WithClientEncryptionPolicy() .WithIncludedPath(path1) .WithIncludedPath(path2) @@ -149,7 +207,7 @@ await database.DefineContainer(Program.encryptedcontainerId, "/AccountNumber") .CreateAsync(throughput: 1000); // gets a Container with Encryption Support. - containerWithEncryption = await database.GetContainer(Program.encryptedcontainerId).InitializeEncryptionAsync(); + containerWithEncryption = await database.GetContainer(Program.encryptedContainerId).InitializeEncryptionAsync(); } private static async Task RunDemoAsync() @@ -248,41 +306,13 @@ private static SalesOrder GetSalesOrderSample(string account, string orderId) return salesOrder; } - private static async Task CleanupAsync() { if (Program.client != null) { - await Program.client.GetDatabase(encrypteddatabaseId).DeleteStreamAsync(); + await Program.client.GetDatabase(encryptedDatabaseId).DeleteStreamAsync(); client.Dispose(); } } } - - internal class TestEncryptionKeyStoreProvider : EncryptionKeyStoreProvider - { - public override string ProviderName => "TESTKEYSTORE_VAULT"; - - public override byte[] UnwrapKey(string masterKeyPath, KeyEncryptionKeyAlgorithm encryptionAlgorithm, byte[] encryptedKey) - { - byte[] plainkey = encryptedKey.Select(b => (byte)(b - 1)).ToArray(); - return plainkey; - } - - public override byte[] WrapKey(string masterKeyPath, KeyEncryptionKeyAlgorithm encryptionAlgorithm, byte[] key) - { - byte[] encryptedkey = key.Select(b => (byte)(b + 1)).ToArray(); - return encryptedkey; - } - - public override byte[] Sign(string masterKeyPath, bool allowEnclaveComputations) - { - return null; - } - - public override bool Verify(string masterKeyPath, bool allowEnclaveComputations, byte[] signature) - { - return true; - } - } } diff --git a/Microsoft.Azure.Cosmos/contracts/API_3.19.0-preview1.txt b/Microsoft.Azure.Cosmos/contracts/API_3.19.0-preview1.txt new file mode 100644 index 0000000000..b01381b429 --- /dev/null +++ b/Microsoft.Azure.Cosmos/contracts/API_3.19.0-preview1.txt @@ -0,0 +1,1465 @@ +namespace Microsoft.Azure.Cosmos +{ + public class AccountConsistency + { + public AccountConsistency(); + public ConsistencyLevel DefaultConsistencyLevel { get; } + public int MaxStalenessIntervalInSeconds { get; } + public int MaxStalenessPrefix { get; } + } + public class AccountProperties + { + public AccountConsistency Consistency { get; } + public string ETag { get; } + public string Id { get; } + public IEnumerable ReadableRegions { get; } + public IEnumerable WritableRegions { get; } + } + public class AccountRegion + { + public AccountRegion(); + public string Endpoint { get; } + public string Name { get; } + } + public sealed class BoundingBoxProperties + { + public BoundingBoxProperties(); + public double Xmax { get; set; } + public double Xmin { get; set; } + public double Ymax { get; set; } + public double Ymin { get; set; } + } + public abstract class ChangeFeedEstimator + { + protected ChangeFeedEstimator(); + public abstract FeedIterator GetCurrentStateIterator(ChangeFeedEstimatorRequestOptions changeFeedEstimatorRequestOptions=null); + } + public sealed class ChangeFeedEstimatorRequestOptions + { + public ChangeFeedEstimatorRequestOptions(); + public Nullable MaxItemCount { get; set; } + } + public abstract class ChangeFeedMode + { + public static ChangeFeedMode FullFidelity { get; } + public static ChangeFeedMode Incremental { get; } + } + public sealed class ChangeFeedPolicy + { + public ChangeFeedPolicy(); + public static TimeSpan FullFidelityNoRetention { get; } + public TimeSpan FullFidelityRetention { get; set; } + } + public abstract class ChangeFeedProcessor + { + protected ChangeFeedProcessor(); + public abstract Task StartAsync(); + public abstract Task StopAsync(); + } + public class ChangeFeedProcessorBuilder + { + public ChangeFeedProcessor Build(); + public ChangeFeedProcessorBuilder WithInstanceName(string instanceName); + public ChangeFeedProcessorBuilder WithLeaseConfiguration(Nullable acquireInterval=default(Nullable), Nullable expirationInterval=default(Nullable), Nullable renewInterval=default(Nullable)); + public ChangeFeedProcessorBuilder WithLeaseContainer(Container leaseContainer); + public ChangeFeedProcessorBuilder WithMaxItems(int maxItemCount); + public ChangeFeedProcessorBuilder WithPollInterval(TimeSpan pollInterval); + public ChangeFeedProcessorBuilder WithStartTime(DateTime startTime); + } + public abstract class ChangeFeedProcessorContext + { + protected ChangeFeedProcessorContext(); + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract Headers Headers { get; } + public abstract string LeaseToken { get; } + } + public sealed class ChangeFeedProcessorState + { + public ChangeFeedProcessorState(string leaseToken, long estimatedLag, string instanceName); + public long EstimatedLag { get; } + public string InstanceName { get; } + public string LeaseToken { get; } + } + public sealed class ChangeFeedRequestOptions : RequestOptions + { + public ChangeFeedRequestOptions(); + public bool EmitOldContinuationToken { get; set; } + public new string IfMatchEtag { get; set; } + public new string IfNoneMatchEtag { get; set; } + public Nullable PageSizeHint { get; set; } + } + public abstract class ChangeFeedStartFrom + { + public static ChangeFeedStartFrom Beginning(); + public static ChangeFeedStartFrom Beginning(FeedRange feedRange); + public static ChangeFeedStartFrom ContinuationToken(string continuationToken); + public static ChangeFeedStartFrom Now(); + public static ChangeFeedStartFrom Now(FeedRange feedRange); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc); + public static ChangeFeedStartFrom Time(DateTime dateTimeUtc, FeedRange feedRange); + } + public sealed class ClientEncryptionIncludedPath + { + public ClientEncryptionIncludedPath(); + public string ClientEncryptionKeyId { get; set; } + public string EncryptionAlgorithm { get; set; } + public string EncryptionType { get; set; } + public string Path { get; set; } + } + public abstract class ClientEncryptionKey + { + protected ClientEncryptionKey(); + public abstract string Id { get; } + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(ClientEncryptionKeyProperties clientEncryptionKeyProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class ClientEncryptionKeyProperties : IEquatable + { + protected ClientEncryptionKeyProperties(); + public ClientEncryptionKeyProperties(string id, string encryptionAlgorithm, byte[] wrappedDataEncryptionKey, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata); + public Nullable CreatedTime { get; } + public string EncryptionAlgorithm { get; } + public EncryptionKeyWrapMetadata EncryptionKeyWrapMetadata { get; } + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public virtual string SelfLink { get; } + public byte[] WrappedDataEncryptionKey { get; } + public bool Equals(ClientEncryptionKeyProperties other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public class ClientEncryptionKeyResponse : Response + { + protected ClientEncryptionKeyResponse(); + public override string ActivityId { get; } + public virtual ClientEncryptionKey ClientEncryptionKey { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ClientEncryptionKeyProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ClientEncryptionKey (ClientEncryptionKeyResponse response); + } + public sealed class ClientEncryptionPolicy + { + public ClientEncryptionPolicy(IEnumerable includedPaths); + public IEnumerable IncludedPaths { get; } + public int PolicyFormatVersion { get; } + } + public sealed class CompositePath + { + public CompositePath(); + public CompositePathSortOrder Order { get; set; } + public string Path { get; set; } + } + public enum CompositePathSortOrder + { + Ascending = 0, + Descending = 1, + } + public class ConflictProperties + { + public ConflictProperties(); + public string Id { get; } + public OperationKind OperationKind { get; } + public string SelfLink { get; } + } + public enum ConflictResolutionMode + { + Custom = 1, + LastWriterWins = 0, + } + public class ConflictResolutionPolicy + { + public ConflictResolutionPolicy(); + public ConflictResolutionMode Mode { get; set; } + public string ResolutionPath { get; set; } + public string ResolutionProcedure { get; set; } + } + public abstract class Conflicts + { + protected Conflicts(); + public abstract Task DeleteAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetConflictQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract T ReadConflictContent(ConflictProperties conflict); + public abstract Task> ReadCurrentAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum ConnectionMode + { + Direct = 1, + Gateway = 0, + } + public enum ConsistencyLevel + { + BoundedStaleness = 1, + ConsistentPrefix = 4, + Eventual = 3, + Session = 2, + Strong = 0, + } + public abstract class Container + { + protected Container(); + public abstract Conflicts Conflicts { get; } + public abstract Database Database { get; } + public abstract string Id { get; } + public abstract Scripts Scripts { get; } + public abstract Task> CreateItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey); + public abstract Task DeleteContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> DeleteItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ChangeFeedEstimator GetChangeFeedEstimator(string processorName, Container leaseContainer); + public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder(string processorName, Container.ChangesEstimationHandler estimationDelegate, Nullable estimationPeriod=default(Nullable)); + public abstract FeedIterator GetChangeFeedIterator(ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions=null); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangeFeedStreamHandler onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint(string processorName, Container.ChangeFeedStreamHandlerWithManualCheckpoint onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilderWithManualCheckpoint(string processorName, Container.ChangeFeedHandlerWithManualCheckpoint onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangeFeedHandler onChangesDelegate); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangesHandler onChangesDelegate); + public abstract FeedIterator GetChangeFeedStreamIterator(ChangeFeedStartFrom changeFeedStartFrom, ChangeFeedMode changeFeedMode, ChangeFeedRequestOptions changeFeedRequestOptions=null); + public abstract Task> GetFeedRangesAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract IOrderedQueryable GetItemLinqQueryable(bool allowSynchronousQueryExecution=false, string continuationToken=null, QueryRequestOptions requestOptions=null, CosmosLinqSerializerOptions linqSerializerOptions=null); + public abstract FeedIterator GetItemQueryIterator(FeedRange feedRange, QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(FeedRange feedRange, QueryDefinition queryDefinition, string continuationToken, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task> GetPartitionKeyRangesAsync(FeedRange feedRange, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> PatchItemAsync(string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task PatchItemStreamAsync(string id, PartitionKey partitionKey, IReadOnlyList patchOperations, PatchItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadManyItemsAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadManyItemsStreamAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerStreamAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReplaceItemAsync(T item, string id, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceItemStreamAsync(Stream streamPayload, string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> UpsertItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public delegate Task ChangeFeedHandlerWithManualCheckpoint(ChangeFeedProcessorContext context, IReadOnlyCollection changes, Func>> tryCheckpointAsync, CancellationToken cancellationToken); + public delegate Task ChangeFeedHandler(ChangeFeedProcessorContext context, IReadOnlyCollection changes, CancellationToken cancellationToken); + public delegate Task ChangeFeedStreamHandler(ChangeFeedProcessorContext context, Stream changes, CancellationToken cancellationToken); + public delegate Task ChangeFeedStreamHandlerWithManualCheckpoint(ChangeFeedProcessorContext context, Stream changes, Func>> tryCheckpointAsync, CancellationToken cancellationToken); + public delegate Task ChangesEstimationHandler(long estimatedPendingChanges, CancellationToken cancellationToken); + public delegate Task ChangesHandler(IReadOnlyCollection changes, CancellationToken cancellationToken); + } + public class ContainerProperties + { + public ContainerProperties(); + public ContainerProperties(string id, IReadOnlyList partitionKeyPaths); + public ContainerProperties(string id, string partitionKeyPath); + public Nullable AnalyticalStoreTimeToLiveInSeconds { get; set; } + public ChangeFeedPolicy ChangeFeedPolicy { get; set; } + public ClientEncryptionPolicy ClientEncryptionPolicy { get; set; } + public ConflictResolutionPolicy ConflictResolutionPolicy { get; set; } + public Nullable DefaultTimeToLive { get; set; } + public string ETag { get; } + public GeospatialConfig GeospatialConfig { get; set; } + public string Id { get; set; } + public IndexingPolicy IndexingPolicy { get; set; } + public Nullable LastModified { get; } + public Nullable PartitionKeyDefinitionVersion { get; set; } + public string PartitionKeyPath { get; set; } + public IReadOnlyList PartitionKeyPaths { get; set; } + public string SelfLink { get; } + public string TimeToLivePropertyPath { get; set; } + public UniqueKeyPolicy UniqueKeyPolicy { get; set; } + } + public class ContainerRequestOptions : RequestOptions + { + public ContainerRequestOptions(); + public bool PopulateQuotaInfo { get; set; } + } + public class ContainerResponse : Response + { + protected ContainerResponse(); + public override string ActivityId { get; } + public virtual Container Container { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ContainerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Container (ContainerResponse response); + } + public class CosmosClient : IDisposable + { + protected CosmosClient(); + public CosmosClient(string accountEndpoint, TokenCredential tokenCredential, CosmosClientOptions clientOptions=null); + public CosmosClient(string connectionString, CosmosClientOptions clientOptions=null); + public CosmosClient(string accountEndpoint, string authKeyOrResourceToken, CosmosClientOptions clientOptions=null); + public virtual CosmosClientOptions ClientOptions { get; } + public virtual Uri Endpoint { get; } + public virtual CosmosResponseFactory ResponseFactory { get; } + public static Task CreateAndInitializeAsync(string accountEndpoint, TokenCredential tokenCredential, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string connectionString, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string accountEndpoint, string authKeyOrResourceToken, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseStreamAsync(DatabaseProperties databaseProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual Container GetContainer(string databaseId, string containerId); + public virtual Database GetDatabase(string id); + public virtual FeedIterator GetDatabaseQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual Task ReadAccountAsync(); + } + public class CosmosClientOptions + { + public CosmosClientOptions(); + public bool AllowBulkExecution { get; set; } + public string ApplicationName { get; set; } + public IReadOnlyList ApplicationPreferredRegions { get; set; } + public string ApplicationRegion { get; set; } + public ConnectionMode ConnectionMode { get; set; } + public Nullable ConsistencyLevel { get; set; } + public Collection CustomHandlers { get; } + public Nullable EnableContentResponseOnWrite { get; set; } + public bool EnableTcpConnectionEndpointRediscovery { get; set; } + public int GatewayModeMaxConnectionLimit { get; set; } + public Func HttpClientFactory { get; set; } + public Nullable IdleTcpConnectionTimeout { get; set; } + public bool LimitToEndpoint { get; set; } + public Nullable MaxRequestsPerTcpConnection { get; set; } + public Nullable MaxRetryAttemptsOnRateLimitedRequests { get; set; } + public Nullable MaxRetryWaitTimeOnRateLimitedRequests { get; set; } + public Nullable MaxTcpConnectionsPerEndpoint { get; set; } + public Nullable OpenTcpConnectionTimeout { get; set; } + public Nullable PortReuseMode { get; set; } + public TimeSpan RequestTimeout { get; set; } + public CosmosSerializer Serializer { get; set; } + public CosmosSerializationOptions SerializerOptions { get; set; } + public Nullable TokenCredentialBackgroundRefreshInterval { get; set; } + public IWebProxy WebProxy { get; set; } + } + public abstract class CosmosDiagnostics + { + protected CosmosDiagnostics(); + public virtual TimeSpan GetClientElapsedTime(); + public abstract IReadOnlyList> GetContactedRegions(); + public abstract override string ToString(); + } + public class CosmosException : Exception + { + public CosmosException(string message, HttpStatusCode statusCode, int subStatusCode, string activityId, double requestCharge); + public virtual string ActivityId { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual Headers Headers { get; } + public virtual double RequestCharge { get; } + public virtual string ResponseBody { get; } + public virtual Nullable RetryAfter { get; } + public override string StackTrace { get; } + public virtual HttpStatusCode StatusCode { get; } + public virtual int SubStatusCode { get; } + public override string ToString(); + public virtual bool TryGetHeader(string headerName, out string value); + } + public sealed class CosmosLinqSerializerOptions + { + public CosmosLinqSerializerOptions(); + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public class CosmosOperationCanceledException : OperationCanceledException + { + public CosmosOperationCanceledException(OperationCanceledException originalException, CosmosDiagnostics diagnostics); + public override IDictionary Data { get; } + public CosmosDiagnostics Diagnostics { get; } + public override string HelpLink { get; set; } + public override string Message { get; } + public override string Source { get; set; } + public override string StackTrace { get; } + public override Exception GetBaseException(); + public override string ToString(); + } + public enum CosmosPropertyNamingPolicy + { + CamelCase = 1, + Default = 0, + } + public abstract class CosmosResponseFactory + { + protected CosmosResponseFactory(); + public abstract FeedResponse CreateItemFeedResponse(ResponseMessage responseMessage); + public abstract ItemResponse CreateItemResponse(ResponseMessage responseMessage); + public abstract StoredProcedureExecuteResponse CreateStoredProcedureExecuteResponse(ResponseMessage responseMessage); + } + public sealed class CosmosSerializationOptions + { + public CosmosSerializationOptions(); + public bool IgnoreNullValues { get; set; } + public bool Indented { get; set; } + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public abstract class CosmosSerializer + { + protected CosmosSerializer(); + public abstract T FromStream(Stream stream); + public abstract Stream ToStream(T input); + } + public abstract class Database + { + protected Database(); + public abstract CosmosClient Client { get; } + public abstract string Id { get; } + public abstract Task CreateClientEncryptionKeyAsync(ClientEncryptionKeyProperties clientEncryptionKeyProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ContainerBuilder DefineContainer(string name, string partitionKeyPath); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ClientEncryptionKey GetClientEncryptionKey(string id); + public abstract FeedIterator GetClientEncryptionKeyQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Container GetContainer(string id); + public abstract FeedIterator GetContainerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract User GetUser(string id); + public abstract FeedIterator GetUserQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class DatabaseProperties + { + public DatabaseProperties(); + public DatabaseProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class DatabaseResponse : Response + { + protected DatabaseResponse(); + public override string ActivityId { get; } + public virtual Database Database { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override DatabaseProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Database (DatabaseResponse response); + } + public enum DataType + { + LineString = 3, + MultiPolygon = 5, + Number = 0, + Point = 2, + Polygon = 4, + String = 1, + } + public class DedicatedGatewayRequestOptions + { + public DedicatedGatewayRequestOptions(); + public Nullable MaxIntegratedCacheStaleness { get; set; } + } + public class EncryptionKeyWrapMetadata : IEquatable + { + public EncryptionKeyWrapMetadata(EncryptionKeyWrapMetadata source); + public EncryptionKeyWrapMetadata(string type, string name, string value); + public string Name { get; } + public string Type { get; } + public string Value { get; } + public bool Equals(EncryptionKeyWrapMetadata other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class ExcludedPath + { + public ExcludedPath(); + public string Path { get; set; } + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task> ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedRange + { + protected FeedRange(); + public static FeedRange FromJsonString(string toStringValue); + public static FeedRange FromPartitionKey(PartitionKey partitionKey); + public abstract string ToJsonString(); + } + public abstract class FeedResponse : IEnumerable, IEnumerable + { + protected FeedResponse(); + public override string ActivityId { get; } + public abstract string ContinuationToken { get; } + public abstract int Count { get; } + public override string ETag { get; } + public override double RequestCharge { get; } + public abstract IEnumerator GetEnumerator(); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public sealed class GeospatialConfig + { + public GeospatialConfig(); + public GeospatialConfig(GeospatialType geospatialType); + public GeospatialType GeospatialType { get; set; } + } + public enum GeospatialType + { + Geography = 0, + Geometry = 1, + } + public class Headers : IEnumerable + { + public Headers(); + public virtual string ActivityId { get; } + public virtual string ContentLength { get; set; } + public virtual string ContentType { get; } + public virtual string ContinuationToken { get; } + public virtual string ETag { get; } + public virtual string this[string headerName] { get; set; } + public virtual string Location { get; } + public virtual double RequestCharge { get; } + public virtual string Session { get; } + public virtual void Add(string headerName, IEnumerable values); + public virtual void Add(string headerName, string value); + public virtual string[] AllKeys(); + public virtual string Get(string headerName); + public virtual IEnumerator GetEnumerator(); + public virtual T GetHeaderValue(string headerName); + public virtual string GetValueOrDefault(string headerName); + public virtual void Remove(string headerName); + public virtual void Set(string headerName, string value); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + public virtual bool TryGetValue(string headerName, out string value); + } + public sealed class IncludedPath + { + public IncludedPath(); + public string Path { get; set; } + } + public enum IndexingDirective + { + Default = 0, + Exclude = 2, + Include = 1, + } + public enum IndexingMode + { + Consistent = 0, + Lazy = 1, + None = 2, + } + public sealed class IndexingPolicy + { + public IndexingPolicy(); + public bool Automatic { get; set; } + public Collection> CompositeIndexes { get; } + public Collection ExcludedPaths { get; } + public Collection IncludedPaths { get; } + public IndexingMode IndexingMode { get; set; } + public Collection SpatialIndexes { get; } + } + public enum IndexKind + { + Hash = 0, + Range = 1, + Spatial = 2, + } + public class ItemRequestOptions : RequestOptions + { + public ItemRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public DedicatedGatewayRequestOptions DedicatedGatewayRequestOptions { get; set; } + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + public IEnumerable PostTriggers { get; set; } + public IEnumerable PreTriggers { get; set; } + public string SessionToken { get; set; } + } + public class ItemResponse : Response + { + protected ItemResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public override HttpStatusCode StatusCode { get; } + } + public enum OperationKind + { + Create = 1, + Delete = 3, + Invalid = 0, + Read = 4, + Replace = 2, + } + public struct PartitionKey : IEquatable + { + public static readonly PartitionKey None; + public static readonly PartitionKey Null; + public static readonly string SystemKeyName; + public static readonly string SystemKeyPath; + public PartitionKey(bool partitionKeyValue); + public PartitionKey(double partitionKeyValue); + public PartitionKey(string partitionKeyValue); + public bool Equals(PartitionKey other); + public override bool Equals(object obj); + public override int GetHashCode(); + public static bool operator ==(PartitionKey left, PartitionKey right); + public static bool operator !=(PartitionKey left, PartitionKey right); + public override string ToString(); + } + public sealed class PartitionKeyBuilder + { + public PartitionKeyBuilder(); + public PartitionKeyBuilder Add(bool val); + public PartitionKeyBuilder Add(double val); + public PartitionKeyBuilder Add(string val); + public PartitionKeyBuilder AddNoneType(); + public PartitionKeyBuilder AddNullValue(); + public PartitionKey Build(); + } + public enum PartitionKeyDefinitionVersion + { + V1 = 1, + V2 = 2, + } + public sealed class PatchItemRequestOptions : ItemRequestOptions + { + public PatchItemRequestOptions(); + public string FilterPredicate { get; set; } + } + public abstract class PatchOperation + { + protected PatchOperation(); + public abstract PatchOperationType OperationType { get; } + public abstract string Path { get; } + public static PatchOperation Add(string path, T value); + public static PatchOperation Increment(string path, double value); + public static PatchOperation Increment(string path, long value); + public static PatchOperation Remove(string path); + public static PatchOperation Replace(string path, T value); + public static PatchOperation Set(string path, T value); + public virtual bool TrySerializeValueParameter(CosmosSerializer cosmosSerializer, out Stream valueParam); + } + public enum PatchOperationType + { + Add = 0, + Increment = 4, + Remove = 1, + Replace = 2, + Set = 3, + } + public abstract class Permission + { + protected Permission(); + public abstract string Id { get; } + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadAsync(Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum PermissionMode : byte + { + All = (byte)2, + Read = (byte)1, + } + public class PermissionProperties + { + public PermissionProperties(string id, PermissionMode permissionMode, Container container, PartitionKey resourcePartitionKey, string itemId); + public PermissionProperties(string id, PermissionMode permissionMode, Container container, Nullable resourcePartitionKey=default(Nullable)); + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public PermissionMode PermissionMode { get; } + public Nullable ResourcePartitionKey { get; set; } + public string ResourceUri { get; } + public string SelfLink { get; } + public string Token { get; } + } + public class PermissionResponse : Response + { + protected PermissionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public virtual Permission Permission { get; } + public override double RequestCharge { get; } + public override PermissionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Permission (PermissionResponse response); + } + public enum PortReuseMode + { + PrivatePortPool = 1, + ReuseUnicastPort = 0, + } + public class QueryDefinition + { + public QueryDefinition(string query); + public string QueryText { get; } + public IReadOnlyList> GetQueryParameters(); + public QueryDefinition WithParameter(string name, object value); + public QueryDefinition WithParameterStream(string name, Stream valueStream); + } + public class QueryRequestOptions : RequestOptions + { + public QueryRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public DedicatedGatewayRequestOptions DedicatedGatewayRequestOptions { get; set; } + public Nullable EnableLowPrecisionOrderBy { get; set; } + public Nullable EnableScanInQuery { get; set; } + public Nullable MaxBufferedItemCount { get; set; } + public Nullable MaxConcurrency { get; set; } + public Nullable MaxItemCount { get; set; } + public Nullable PartitionKey { get; set; } + public Nullable ResponseContinuationTokenLimitInKb { get; set; } + public string SessionToken { get; set; } + } + public class ReadManyRequestOptions : RequestOptions + { + public ReadManyRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public static class Regions + { + public const string AustraliaCentral = "Australia Central"; + public const string AustraliaCentral2 = "Australia Central 2"; + public const string AustraliaEast = "Australia East"; + public const string AustraliaSoutheast = "Australia Southeast"; + public const string BrazilSouth = "Brazil South"; + public const string BrazilSoutheast = "Brazil Southeast"; + public const string CanadaCentral = "Canada Central"; + public const string CanadaEast = "Canada East"; + public const string CentralIndia = "Central India"; + public const string CentralUS = "Central US"; + public const string CentralUSEUAP = "Central US EUAP"; + public const string ChinaEast = "China East"; + public const string ChinaEast2 = "China East 2"; + public const string ChinaNorth = "China North"; + public const string ChinaNorth2 = "China North 2"; + public const string EastAsia = "East Asia"; + public const string EastUS = "East US"; + public const string EastUS2 = "East US 2"; + public const string EastUS2EUAP = "East US 2 EUAP"; + public const string EastUSSLV = "East US SLV"; + public const string FranceCentral = "France Central"; + public const string FranceSouth = "France South"; + public const string GermanyCentral = "Germany Central"; + public const string GermanyNorth = "Germany North"; + public const string GermanyNortheast = "Germany Northeast"; + public const string GermanyWestCentral = "Germany West Central"; + public const string JapanEast = "Japan East"; + public const string JapanWest = "Japan West"; + public const string JioIndiaCentral = "Jio India Central"; + public const string JioIndiaWest = "Jio India West"; + public const string KoreaCentral = "Korea Central"; + public const string KoreaSouth = "Korea South"; + public const string NorthCentralUS = "North Central US"; + public const string NorthEurope = "North Europe"; + public const string NorwayEast = "Norway East"; + public const string NorwayWest = "Norway West"; + public const string SouthAfricaNorth = "South Africa North"; + public const string SouthAfricaWest = "South Africa West"; + public const string SouthCentralUS = "South Central US"; + public const string SoutheastAsia = "Southeast Asia"; + public const string SouthIndia = "South India"; + public const string SwitzerlandNorth = "Switzerland North"; + public const string SwitzerlandWest = "Switzerland West"; + public const string UAECentral = "UAE Central"; + public const string UAENorth = "UAE North"; + public const string UKSouth = "UK South"; + public const string UKWest = "UK West"; + public const string USDoDCentral = "USDoD Central"; + public const string USDoDEast = "USDoD East"; + public const string USGovArizona = "USGov Arizona"; + public const string USGovTexas = "USGov Texas"; + public const string USGovVirginia = "USGov Virginia"; + public const string USNatEast = "USNat East"; + public const string USNatWest = "USNat West"; + public const string USSecEast = "USSec East"; + public const string USSecWest = "USSec West"; + public const string WestCentralUS = "West Central US"; + public const string WestEurope = "West Europe"; + public const string WestIndia = "West India"; + public const string WestUS = "West US"; + public const string WestUS2 = "West US 2"; + public const string WestUS3 = "West US 3"; + } + public abstract class RequestHandler + { + protected RequestHandler(); + public RequestHandler InnerHandler { get; set; } + public virtual Task SendAsync(RequestMessage request, CancellationToken cancellationToken); + } + public class RequestMessage : IDisposable + { + public RequestMessage(); + public RequestMessage(HttpMethod method, Uri requestUri); + public virtual Stream Content { get; set; } + public virtual Headers Headers { get; } + public virtual HttpMethod Method { get; } + public virtual Dictionary Properties { get; } + public virtual Uri RequestUri { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + } + public class RequestOptions + { + public RequestOptions(); + public Action AddRequestHeaders { get; set; } + public string IfMatchEtag { get; set; } + public string IfNoneMatchEtag { get; set; } + public IReadOnlyDictionary Properties { get; set; } + public RequestOptions ShallowCopy(); + } + public class ResponseMessage : IDisposable + { + public ResponseMessage(); + public ResponseMessage(HttpStatusCode statusCode, RequestMessage requestMessage=null, string errorMessage=null); + public virtual Stream Content { get; set; } + public virtual string ContinuationToken { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual RequestMessage RequestMessage { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual ResponseMessage EnsureSuccessStatusCode(); + } + public abstract class Response + { + protected Response(); + public abstract string ActivityId { get; } + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract string ETag { get; } + public abstract Headers Headers { get; } + public abstract double RequestCharge { get; } + public abstract T Resource { get; } + public abstract HttpStatusCode StatusCode { get; } + public static implicit operator T (Response response); + } + public sealed class SpatialPath + { + public SpatialPath(); + public BoundingBoxProperties BoundingBox { get; set; } + public string Path { get; set; } + public Collection SpatialTypes { get; } + } + public enum SpatialType + { + LineString = 1, + MultiPolygon = 3, + Point = 0, + Polygon = 2, + } + public class ThroughputProperties + { + public Nullable AutoscaleMaxThroughput { get; } + public string ETag { get; } + public Nullable LastModified { get; } + public string SelfLink { get; } + public Nullable Throughput { get; } + public static ThroughputProperties CreateAutoscaleThroughput(int autoscaleMaxThroughput); + public static ThroughputProperties CreateManualThroughput(int throughput); + } + public class ThroughputResponse : Response + { + protected ThroughputResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public Nullable IsReplacePending { get; } + public Nullable MinThroughput { get; } + public override double RequestCharge { get; } + public override ThroughputProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ThroughputProperties (ThroughputResponse response); + } + public abstract class TransactionalBatch + { + protected TransactionalBatch(); + public abstract TransactionalBatch CreateItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch CreateItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch DeleteItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract Task ExecuteAsync(TransactionalBatchRequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch PatchItem(string id, IReadOnlyList patchOperations, TransactionalBatchPatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReadItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItemStream(string id, Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItem(string id, T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + } + public class TransactionalBatchItemRequestOptions : RequestOptions + { + public TransactionalBatchItemRequestOptions(); + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + } + public class TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual string ETag { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual Stream ResourceStream { get; } + public virtual TimeSpan RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + } + public class TransactionalBatchOperationResult : TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual T Resource { get; set; } + } + public class TransactionalBatchPatchItemRequestOptions : TransactionalBatchItemRequestOptions + { + public TransactionalBatchPatchItemRequestOptions(); + public string FilterPredicate { get; set; } + } + public class TransactionalBatchRequestOptions : RequestOptions + { + public TransactionalBatchRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public class TransactionalBatchResponse : IDisposable, IEnumerable, IEnumerable, IReadOnlyCollection, IReadOnlyList + { + protected TransactionalBatchResponse(); + public virtual string ActivityId { get; } + public virtual int Count { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual TransactionalBatchOperationResult this[int index] { get; } + public virtual double RequestCharge { get; } + public virtual Nullable RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual IEnumerator GetEnumerator(); + public virtual TransactionalBatchOperationResult GetOperationResultAtIndex(int index); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public class UniqueKey + { + public UniqueKey(); + public Collection Paths { get; } + } + public sealed class UniqueKeyPolicy + { + public UniqueKeyPolicy(); + public Collection UniqueKeys { get; } + } + public abstract class User + { + protected User(); + public abstract string Id { get; } + public abstract Task CreatePermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Permission GetPermission(string id); + public abstract FeedIterator GetPermissionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetPermissionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(UserProperties userProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertPermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class UserProperties + { + protected UserProperties(); + public UserProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class UserResponse : Response + { + protected UserResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public virtual User User { get; } + public static implicit operator User (UserResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Fluent +{ + public class ChangeFeedPolicyDefinition + { + public ContainerBuilder Attach(); + } + public sealed class ClientEncryptionPolicyDefinition + { + public ContainerBuilder Attach(); + public ClientEncryptionPolicyDefinition WithIncludedPath(ClientEncryptionIncludedPath path); + } + public class CompositeIndexDefinition + { + public T Attach(); + public CompositeIndexDefinition Path(string path); + public CompositeIndexDefinition Path(string path, CompositePathSortOrder sortOrder); + } + public class ConflictResolutionDefinition + { + public ContainerBuilder Attach(); + public ConflictResolutionDefinition WithCustomStoredProcedureResolution(string conflictResolutionProcedure); + public ConflictResolutionDefinition WithLastWriterWinsResolution(string conflictResolutionPath); + } + public class ContainerBuilder : ContainerDefinition + { + protected ContainerBuilder(); + public ContainerBuilder(Database database, string name, string partitionKeyPath); + public new ContainerProperties Build(); + public Task CreateAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public ChangeFeedPolicyDefinition WithChangeFeedPolicy(TimeSpan retention); + public ClientEncryptionPolicyDefinition WithClientEncryptionPolicy(); + public ConflictResolutionDefinition WithConflictResolution(); + public UniqueKeyDefinition WithUniqueKey(); + } + public abstract class ContainerDefinition where T : ContainerDefinition + { + public ContainerDefinition(); + public ContainerProperties Build(); + public T WithDefaultTimeToLive(int defaultTtlInSeconds); + public T WithDefaultTimeToLive(TimeSpan defaultTtlTimeSpan); + public IndexingPolicyDefinition WithIndexingPolicy(); + public T WithPartitionKeyDefinitionVersion(PartitionKeyDefinitionVersion partitionKeyDefinitionVersion); + public T WithTimeToLivePropertyPath(string propertyPath); + } + public class CosmosClientBuilder + { + public CosmosClientBuilder(string connectionString); + public CosmosClientBuilder(string accountEndpoint, string authKeyOrResourceToken); + public CosmosClientBuilder AddCustomHandlers(params RequestHandler[] customHandlers); + public CosmosClient Build(); + public CosmosClientBuilder WithApplicationName(string applicationName); + public CosmosClientBuilder WithApplicationPreferredRegions(IReadOnlyList applicationPreferredRegions); + public CosmosClientBuilder WithApplicationRegion(string applicationRegion); + public CosmosClientBuilder WithBulkExecution(bool enabled); + public CosmosClientBuilder WithConnectionModeDirect(); + public CosmosClientBuilder WithConnectionModeDirect(Nullable idleTcpConnectionTimeout=default(Nullable), Nullable openTcpConnectionTimeout=default(Nullable), Nullable maxRequestsPerTcpConnection=default(Nullable), Nullable maxTcpConnectionsPerEndpoint=default(Nullable), Nullable portReuseMode=default(Nullable), Nullable enableTcpConnectionEndpointRediscovery=default(Nullable)); + public CosmosClientBuilder WithConnectionModeGateway(Nullable maxConnectionLimit=default(Nullable), IWebProxy webProxy=null); + public CosmosClientBuilder WithConsistencyLevel(ConsistencyLevel consistencyLevel); + public CosmosClientBuilder WithContentResponseOnWrite(bool contentResponseOnWrite); + public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSerializer); + public CosmosClientBuilder WithHttpClientFactory(Func httpClientFactory); + public CosmosClientBuilder WithLimitToEndpoint(bool limitToEndpoint); + public CosmosClientBuilder WithRequestTimeout(TimeSpan requestTimeout); + public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions); + public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeOnThrottledRequests, int maxRetryAttemptsOnThrottledRequests); + } + public class IndexingPolicyDefinition + { + public IndexingPolicyDefinition(); + public T Attach(); + public IndexingPolicyDefinition WithAutomaticIndexing(bool enabled); + public CompositeIndexDefinition> WithCompositeIndex(); + public PathsDefinition> WithExcludedPaths(); + public PathsDefinition> WithIncludedPaths(); + public IndexingPolicyDefinition WithIndexingMode(IndexingMode indexingMode); + public SpatialIndexDefinition> WithSpatialIndex(); + } + public class PathsDefinition + { + public T Attach(); + public PathsDefinition Path(string path); + } + public class SpatialIndexDefinition + { + public T Attach(); + public SpatialIndexDefinition Path(string path); + public SpatialIndexDefinition Path(string path, params SpatialType[] spatialTypes); + } + public class UniqueKeyDefinition + { + public ContainerBuilder Attach(); + public UniqueKeyDefinition Path(string path); + } +} +namespace Microsoft.Azure.Cosmos.Linq +{ + public static class CosmosLinq + { + public static object InvokeUserDefinedFunction(string udfName, params object[] arguments); + } + public static class CosmosLinqExtensions + { + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> CountAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static bool IsDefined(this object obj); + public static bool IsNull(this object obj); + public static bool IsPrimitive(this object obj); + public static Task> MaxAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> MinAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static FeedIterator ToFeedIterator(this IQueryable query); + public static QueryDefinition ToQueryDefinition(this IQueryable query); + public static QueryDefinition ToQueryDefinition(this IQueryable query, IDictionary namedParameters); + public static FeedIterator ToStreamIterator(this IQueryable query); + } +} +namespace Microsoft.Azure.Cosmos.Scripts +{ + public abstract class Scripts + { + protected Scripts(); + public abstract Task CreateStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ExecuteStoredProcedureAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, Stream streamPayload, PartitionKey partitionKey, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetStoredProcedureQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class StoredProcedureExecuteResponse : Response + { + protected StoredProcedureExecuteResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public virtual string ScriptLog { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + } + public class StoredProcedureProperties + { + public StoredProcedureProperties(); + public StoredProcedureProperties(string id, string body); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class StoredProcedureRequestOptions : RequestOptions + { + public StoredProcedureRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public bool EnableScriptLogging { get; set; } + public string SessionToken { get; set; } + } + public class StoredProcedureResponse : Response + { + protected StoredProcedureResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override StoredProcedureProperties Resource { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator StoredProcedureProperties (StoredProcedureResponse response); + } + public enum TriggerOperation : short + { + All = (short)0, + Create = (short)1, + Delete = (short)3, + Replace = (short)4, + Update = (short)2, + } + public class TriggerProperties + { + public TriggerProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + public TriggerOperation TriggerOperation { get; set; } + public TriggerType TriggerType { get; set; } + } + public class TriggerResponse : Response + { + protected TriggerResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override TriggerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator TriggerProperties (TriggerResponse response); + } + public enum TriggerType : byte + { + Post = (byte)1, + Pre = (byte)0, + } + public class UserDefinedFunctionProperties + { + public UserDefinedFunctionProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + } + public class UserDefinedFunctionResponse : Response + { + protected UserDefinedFunctionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserDefinedFunctionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator UserDefinedFunctionProperties (UserDefinedFunctionResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Spatial +{ + public sealed class BoundingBox : IEquatable + { + public BoundingBox(Position min, Position max); + public Position Max { get; } + public Position Min { get; } + public bool Equals(BoundingBox other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public abstract class Crs + { + protected Crs(CrsType type); + public static Crs Default { get; } + public CrsType Type { get; } + public static Crs Unspecified { get; } + public static LinkedCrs Linked(string href); + public static LinkedCrs Linked(string href, string type); + public static NamedCrs Named(string name); + } + public enum CrsType + { + Linked = 1, + Named = 0, + Unspecified = 2, + } + public abstract class Geometry + { + protected Geometry(GeometryType type, GeometryParams geometryParams); + public IDictionary AdditionalProperties { get; } + public BoundingBox BoundingBox { get; } + public Crs Crs { get; } + public GeometryType Type { get; } + public double Distance(Geometry to); + public override bool Equals(object obj); + public override int GetHashCode(); + public bool Intersects(Geometry geometry2); + public bool IsValid(); + public GeometryValidationResult IsValidDetailed(); + public bool Within(Geometry outer); + } + public class GeometryParams + { + public GeometryParams(); + public IDictionary AdditionalProperties { get; set; } + public BoundingBox BoundingBox { get; set; } + public Crs Crs { get; set; } + } + public enum GeometryShape + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public enum GeometryType + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public class GeometryValidationResult + { + public GeometryValidationResult(); + public bool IsValid { get; } + public string Reason { get; } + } + public sealed class LinearRing : IEquatable + { + public LinearRing(IList coordinates); + public ReadOnlyCollection Positions { get; } + public bool Equals(LinearRing other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LineString : Geometry, IEquatable + { + public LineString(IList coordinates); + public LineString(IList coordinates, GeometryParams geometryParams); + public ReadOnlyCollection Positions { get; } + public bool Equals(LineString other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LinkedCrs : Crs, IEquatable + { + public string Href { get; } + public string HrefType { get; } + public bool Equals(LinkedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class MultiPolygon : Geometry, IEquatable + { + public MultiPolygon(IList polygons); + public MultiPolygon(IList polygons, GeometryParams geometryParams); + public ReadOnlyCollection Polygons { get; } + public bool Equals(MultiPolygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class NamedCrs : Crs, IEquatable + { + public string Name { get; } + public bool Equals(NamedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Point : Geometry, IEquatable + { + public Point(Position position); + public Point(Position position, GeometryParams geometryParams); + public Point(double longitude, double latitude); + public Position Position { get; } + public bool Equals(Point other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Polygon : Geometry, IEquatable + { + public Polygon(IList rings); + public Polygon(IList rings, GeometryParams geometryParams); + public Polygon(IList externalRingPositions); + public ReadOnlyCollection Rings { get; } + public bool Equals(Polygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class PolygonCoordinates : IEquatable + { + public PolygonCoordinates(IList rings); + public ReadOnlyCollection Rings { get; } + public bool Equals(PolygonCoordinates other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Position : IEquatable + { + public Position(IList coordinates); + public Position(double longitude, double latitude); + public Position(double longitude, double latitude, Nullable altitude); + public Nullable Altitude { get; } + public ReadOnlyCollection Coordinates { get; } + public double Latitude { get; } + public double Longitude { get; } + public bool Equals(Position other); + public override bool Equals(object obj); + public override int GetHashCode(); + } +} diff --git a/Microsoft.Azure.Cosmos/contracts/API_3.19.0.txt b/Microsoft.Azure.Cosmos/contracts/API_3.19.0.txt new file mode 100644 index 0000000000..92022aca44 --- /dev/null +++ b/Microsoft.Azure.Cosmos/contracts/API_3.19.0.txt @@ -0,0 +1,1267 @@ +namespace Microsoft.Azure.Cosmos +{ + public class AccountConsistency + { + public AccountConsistency(); + public ConsistencyLevel DefaultConsistencyLevel { get; } + public int MaxStalenessIntervalInSeconds { get; } + public int MaxStalenessPrefix { get; } + } + public class AccountProperties + { + public AccountConsistency Consistency { get; } + public string ETag { get; } + public string Id { get; } + public IEnumerable ReadableRegions { get; } + public IEnumerable WritableRegions { get; } + } + public class AccountRegion + { + public AccountRegion(); + public string Endpoint { get; } + public string Name { get; } + } + public sealed class BoundingBoxProperties + { + public BoundingBoxProperties(); + public double Xmax { get; set; } + public double Xmin { get; set; } + public double Ymax { get; set; } + public double Ymin { get; set; } + } + public abstract class ChangeFeedEstimator + { + protected ChangeFeedEstimator(); + public abstract FeedIterator GetCurrentStateIterator(ChangeFeedEstimatorRequestOptions changeFeedEstimatorRequestOptions=null); + } + public sealed class ChangeFeedEstimatorRequestOptions + { + public ChangeFeedEstimatorRequestOptions(); + public Nullable MaxItemCount { get; set; } + } + public abstract class ChangeFeedProcessor + { + protected ChangeFeedProcessor(); + public abstract Task StartAsync(); + public abstract Task StopAsync(); + } + public class ChangeFeedProcessorBuilder + { + public ChangeFeedProcessor Build(); + public ChangeFeedProcessorBuilder WithInstanceName(string instanceName); + public ChangeFeedProcessorBuilder WithLeaseConfiguration(Nullable acquireInterval=default(Nullable), Nullable expirationInterval=default(Nullable), Nullable renewInterval=default(Nullable)); + public ChangeFeedProcessorBuilder WithLeaseContainer(Container leaseContainer); + public ChangeFeedProcessorBuilder WithMaxItems(int maxItemCount); + public ChangeFeedProcessorBuilder WithPollInterval(TimeSpan pollInterval); + public ChangeFeedProcessorBuilder WithStartTime(DateTime startTime); + } + public sealed class ChangeFeedProcessorState + { + public ChangeFeedProcessorState(string leaseToken, long estimatedLag, string instanceName); + public long EstimatedLag { get; } + public string InstanceName { get; } + public string LeaseToken { get; } + } + public sealed class CompositePath + { + public CompositePath(); + public CompositePathSortOrder Order { get; set; } + public string Path { get; set; } + } + public enum CompositePathSortOrder + { + Ascending = 0, + Descending = 1, + } + public class ConflictProperties + { + public ConflictProperties(); + public string Id { get; } + public OperationKind OperationKind { get; } + public string SelfLink { get; } + } + public enum ConflictResolutionMode + { + Custom = 1, + LastWriterWins = 0, + } + public class ConflictResolutionPolicy + { + public ConflictResolutionPolicy(); + public ConflictResolutionMode Mode { get; set; } + public string ResolutionPath { get; set; } + public string ResolutionProcedure { get; set; } + } + public abstract class Conflicts + { + protected Conflicts(); + public abstract Task DeleteAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetConflictQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetConflictQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract T ReadConflictContent(ConflictProperties conflict); + public abstract Task> ReadCurrentAsync(ConflictProperties conflict, PartitionKey partitionKey, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum ConnectionMode + { + Direct = 1, + Gateway = 0, + } + public enum ConsistencyLevel + { + BoundedStaleness = 1, + ConsistentPrefix = 4, + Eventual = 3, + Session = 2, + Strong = 0, + } + public abstract class Container + { + protected Container(); + public abstract Conflicts Conflicts { get; } + public abstract Database Database { get; } + public abstract string Id { get; } + public abstract Scripts Scripts { get; } + public abstract Task> CreateItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch CreateTransactionalBatch(PartitionKey partitionKey); + public abstract Task DeleteContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> DeleteItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ChangeFeedEstimator GetChangeFeedEstimator(string processorName, Container leaseContainer); + public abstract ChangeFeedProcessorBuilder GetChangeFeedEstimatorBuilder(string processorName, Container.ChangesEstimationHandler estimationDelegate, Nullable estimationPeriod=default(Nullable)); + public abstract ChangeFeedProcessorBuilder GetChangeFeedProcessorBuilder(string processorName, Container.ChangesHandler onChangesDelegate); + public abstract IOrderedQueryable GetItemLinqQueryable(bool allowSynchronousQueryExecution=false, string continuationToken=null, QueryRequestOptions requestOptions=null, CosmosLinqSerializerOptions linqSerializerOptions=null); + public abstract FeedIterator GetItemQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetItemQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadContainerAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadContainerStreamAsync(ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadItemAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadItemStreamAsync(string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadManyItemsAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadManyItemsStreamAsync(IReadOnlyList> items, ReadManyRequestOptions readManyRequestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceContainerStreamAsync(ContainerProperties containerProperties, ContainerRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReplaceItemAsync(T item, string id, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceItemStreamAsync(Stream streamPayload, string id, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> UpsertItemAsync(T item, Nullable partitionKey=default(Nullable), ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertItemStreamAsync(Stream streamPayload, PartitionKey partitionKey, ItemRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public delegate Task ChangesEstimationHandler(long estimatedPendingChanges, CancellationToken cancellationToken); + public delegate Task ChangesHandler(IReadOnlyCollection changes, CancellationToken cancellationToken); + } + public class ContainerProperties + { + public ContainerProperties(); + public ContainerProperties(string id, string partitionKeyPath); + public Nullable AnalyticalStoreTimeToLiveInSeconds { get; set; } + public ConflictResolutionPolicy ConflictResolutionPolicy { get; set; } + public Nullable DefaultTimeToLive { get; set; } + public string ETag { get; } + public GeospatialConfig GeospatialConfig { get; set; } + public string Id { get; set; } + public IndexingPolicy IndexingPolicy { get; set; } + public Nullable LastModified { get; } + public Nullable PartitionKeyDefinitionVersion { get; set; } + public string PartitionKeyPath { get; set; } + public string SelfLink { get; } + public string TimeToLivePropertyPath { get; set; } + public UniqueKeyPolicy UniqueKeyPolicy { get; set; } + } + public class ContainerRequestOptions : RequestOptions + { + public ContainerRequestOptions(); + public bool PopulateQuotaInfo { get; set; } + } + public class ContainerResponse : Response + { + protected ContainerResponse(); + public override string ActivityId { get; } + public virtual Container Container { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override ContainerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Container (ContainerResponse response); + } + public class CosmosClient : IDisposable + { + protected CosmosClient(); + public CosmosClient(string accountEndpoint, TokenCredential tokenCredential, CosmosClientOptions clientOptions=null); + public CosmosClient(string connectionString, CosmosClientOptions clientOptions=null); + public CosmosClient(string accountEndpoint, string authKeyOrResourceToken, CosmosClientOptions clientOptions=null); + public virtual CosmosClientOptions ClientOptions { get; } + public virtual Uri Endpoint { get; } + public static Task CreateAndInitializeAsync(string accountEndpoint, TokenCredential tokenCredential, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string connectionString, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public static Task CreateAndInitializeAsync(string accountEndpoint, string authKeyOrResourceToken, IReadOnlyList> containers, CosmosClientOptions cosmosClientOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseIfNotExistsAsync(string id, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public virtual Task CreateDatabaseStreamAsync(DatabaseProperties databaseProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual Container GetContainer(string databaseId, string containerId); + public virtual Database GetDatabase(string id); + public virtual FeedIterator GetDatabaseQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual FeedIterator GetDatabaseQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public virtual Task ReadAccountAsync(); + } + public class CosmosClientOptions + { + public CosmosClientOptions(); + public bool AllowBulkExecution { get; set; } + public string ApplicationName { get; set; } + public IReadOnlyList ApplicationPreferredRegions { get; set; } + public string ApplicationRegion { get; set; } + public ConnectionMode ConnectionMode { get; set; } + public Nullable ConsistencyLevel { get; set; } + public Collection CustomHandlers { get; } + public Nullable EnableContentResponseOnWrite { get; set; } + public bool EnableTcpConnectionEndpointRediscovery { get; set; } + public int GatewayModeMaxConnectionLimit { get; set; } + public Func HttpClientFactory { get; set; } + public Nullable IdleTcpConnectionTimeout { get; set; } + public bool LimitToEndpoint { get; set; } + public Nullable MaxRequestsPerTcpConnection { get; set; } + public Nullable MaxRetryAttemptsOnRateLimitedRequests { get; set; } + public Nullable MaxRetryWaitTimeOnRateLimitedRequests { get; set; } + public Nullable MaxTcpConnectionsPerEndpoint { get; set; } + public Nullable OpenTcpConnectionTimeout { get; set; } + public Nullable PortReuseMode { get; set; } + public TimeSpan RequestTimeout { get; set; } + public CosmosSerializer Serializer { get; set; } + public CosmosSerializationOptions SerializerOptions { get; set; } + public Nullable TokenCredentialBackgroundRefreshInterval { get; set; } + public IWebProxy WebProxy { get; set; } + } + public abstract class CosmosDiagnostics + { + protected CosmosDiagnostics(); + public virtual TimeSpan GetClientElapsedTime(); + public abstract IReadOnlyList> GetContactedRegions(); + public abstract override string ToString(); + } + public class CosmosException : Exception + { + public CosmosException(string message, HttpStatusCode statusCode, int subStatusCode, string activityId, double requestCharge); + public virtual string ActivityId { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual Headers Headers { get; } + public virtual double RequestCharge { get; } + public virtual string ResponseBody { get; } + public virtual Nullable RetryAfter { get; } + public override string StackTrace { get; } + public virtual HttpStatusCode StatusCode { get; } + public virtual int SubStatusCode { get; } + public override string ToString(); + public virtual bool TryGetHeader(string headerName, out string value); + } + public sealed class CosmosLinqSerializerOptions + { + public CosmosLinqSerializerOptions(); + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public class CosmosOperationCanceledException : OperationCanceledException + { + public CosmosOperationCanceledException(OperationCanceledException originalException, CosmosDiagnostics diagnostics); + public override IDictionary Data { get; } + public CosmosDiagnostics Diagnostics { get; } + public override string HelpLink { get; set; } + public override string Message { get; } + public override string Source { get; set; } + public override string StackTrace { get; } + public override Exception GetBaseException(); + public override string ToString(); + } + public enum CosmosPropertyNamingPolicy + { + CamelCase = 1, + Default = 0, + } + public sealed class CosmosSerializationOptions + { + public CosmosSerializationOptions(); + public bool IgnoreNullValues { get; set; } + public bool Indented { get; set; } + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } + public abstract class CosmosSerializer + { + protected CosmosSerializer(); + public abstract T FromStream(Stream stream); + public abstract Stream ToStream(T input); + } + public abstract class Database + { + protected Database(); + public abstract CosmosClient Client { get; } + public abstract string Id { get; } + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerIfNotExistsAsync(string id, string partitionKeyPath, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateContainerStreamAsync(ContainerProperties containerProperties, Nullable throughput=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract ContainerBuilder DefineContainer(string name, string partitionKeyPath); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Container GetContainer(string id); + public abstract FeedIterator GetContainerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetContainerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract User GetUser(string id); + public abstract FeedIterator GetUserQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadStreamAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadThroughputAsync(RequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ReadThroughputAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(ThroughputProperties throughputProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceThroughputAsync(int throughput, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertUserAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class DatabaseProperties + { + public DatabaseProperties(); + public DatabaseProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class DatabaseResponse : Response + { + protected DatabaseResponse(); + public override string ActivityId { get; } + public virtual Database Database { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override DatabaseProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Database (DatabaseResponse response); + } + public enum DataType + { + LineString = 3, + MultiPolygon = 5, + Number = 0, + Point = 2, + Polygon = 4, + String = 1, + } + public sealed class ExcludedPath + { + public ExcludedPath(); + public string Path { get; set; } + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedIterator : IDisposable + { + protected FeedIterator(); + public abstract bool HasMoreResults { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public abstract Task> ReadNextAsync(CancellationToken cancellationToken=default(CancellationToken)); + } + public abstract class FeedResponse : IEnumerable, IEnumerable + { + protected FeedResponse(); + public override string ActivityId { get; } + public abstract string ContinuationToken { get; } + public abstract int Count { get; } + public override string ETag { get; } + public override double RequestCharge { get; } + public abstract IEnumerator GetEnumerator(); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public sealed class GeospatialConfig + { + public GeospatialConfig(); + public GeospatialConfig(GeospatialType geospatialType); + public GeospatialType GeospatialType { get; set; } + } + public enum GeospatialType + { + Geography = 0, + Geometry = 1, + } + public class Headers : IEnumerable + { + public Headers(); + public virtual string ActivityId { get; } + public virtual string ContentLength { get; set; } + public virtual string ContentType { get; } + public virtual string ContinuationToken { get; } + public virtual string ETag { get; } + public virtual string this[string headerName] { get; set; } + public virtual string Location { get; } + public virtual double RequestCharge { get; } + public virtual string Session { get; } + public virtual void Add(string headerName, IEnumerable values); + public virtual void Add(string headerName, string value); + public virtual string[] AllKeys(); + public virtual string Get(string headerName); + public virtual IEnumerator GetEnumerator(); + public virtual T GetHeaderValue(string headerName); + public virtual string GetValueOrDefault(string headerName); + public virtual void Remove(string headerName); + public virtual void Set(string headerName, string value); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + public virtual bool TryGetValue(string headerName, out string value); + } + public sealed class IncludedPath + { + public IncludedPath(); + public string Path { get; set; } + } + public enum IndexingDirective + { + Default = 0, + Exclude = 2, + Include = 1, + } + public enum IndexingMode + { + Consistent = 0, + Lazy = 1, + None = 2, + } + public sealed class IndexingPolicy + { + public IndexingPolicy(); + public bool Automatic { get; set; } + public Collection> CompositeIndexes { get; } + public Collection ExcludedPaths { get; } + public Collection IncludedPaths { get; } + public IndexingMode IndexingMode { get; set; } + public Collection SpatialIndexes { get; } + } + public enum IndexKind + { + Hash = 0, + Range = 1, + Spatial = 2, + } + public class ItemRequestOptions : RequestOptions + { + public ItemRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + public IEnumerable PostTriggers { get; set; } + public IEnumerable PreTriggers { get; set; } + public string SessionToken { get; set; } + } + public class ItemResponse : Response + { + protected ItemResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public override HttpStatusCode StatusCode { get; } + } + public enum OperationKind + { + Create = 1, + Delete = 3, + Invalid = 0, + Read = 4, + Replace = 2, + } + public struct PartitionKey : IEquatable + { + public static readonly PartitionKey None; + public static readonly PartitionKey Null; + public static readonly string SystemKeyName; + public static readonly string SystemKeyPath; + public PartitionKey(bool partitionKeyValue); + public PartitionKey(double partitionKeyValue); + public PartitionKey(string partitionKeyValue); + public bool Equals(PartitionKey other); + public override bool Equals(object obj); + public override int GetHashCode(); + public static bool operator ==(PartitionKey left, PartitionKey right); + public static bool operator !=(PartitionKey left, PartitionKey right); + public override string ToString(); + } + public enum PartitionKeyDefinitionVersion + { + V1 = 1, + V2 = 2, + } + public abstract class Permission + { + protected Permission(); + public abstract string Id { get; } + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadAsync(Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public enum PermissionMode : byte + { + All = (byte)2, + Read = (byte)1, + } + public class PermissionProperties + { + public PermissionProperties(string id, PermissionMode permissionMode, Container container, PartitionKey resourcePartitionKey, string itemId); + public PermissionProperties(string id, PermissionMode permissionMode, Container container, Nullable resourcePartitionKey=default(Nullable)); + public string ETag { get; } + public string Id { get; } + public Nullable LastModified { get; } + public PermissionMode PermissionMode { get; } + public Nullable ResourcePartitionKey { get; set; } + public string ResourceUri { get; } + public string SelfLink { get; } + public string Token { get; } + } + public class PermissionResponse : Response + { + protected PermissionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public virtual Permission Permission { get; } + public override double RequestCharge { get; } + public override PermissionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator Permission (PermissionResponse response); + } + public enum PortReuseMode + { + PrivatePortPool = 1, + ReuseUnicastPort = 0, + } + public class QueryDefinition + { + public QueryDefinition(string query); + public string QueryText { get; } + public IReadOnlyList> GetQueryParameters(); + public QueryDefinition WithParameter(string name, object value); + public QueryDefinition WithParameterStream(string name, Stream valueStream); + } + public class QueryRequestOptions : RequestOptions + { + public QueryRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public Nullable EnableLowPrecisionOrderBy { get; set; } + public Nullable EnableScanInQuery { get; set; } + public Nullable MaxBufferedItemCount { get; set; } + public Nullable MaxConcurrency { get; set; } + public Nullable MaxItemCount { get; set; } + public Nullable PartitionKey { get; set; } + public Nullable ResponseContinuationTokenLimitInKb { get; set; } + public string SessionToken { get; set; } + } + public class ReadManyRequestOptions : RequestOptions + { + public ReadManyRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public static class Regions + { + public const string AustraliaCentral = "Australia Central"; + public const string AustraliaCentral2 = "Australia Central 2"; + public const string AustraliaEast = "Australia East"; + public const string AustraliaSoutheast = "Australia Southeast"; + public const string BrazilSouth = "Brazil South"; + public const string BrazilSoutheast = "Brazil Southeast"; + public const string CanadaCentral = "Canada Central"; + public const string CanadaEast = "Canada East"; + public const string CentralIndia = "Central India"; + public const string CentralUS = "Central US"; + public const string CentralUSEUAP = "Central US EUAP"; + public const string ChinaEast = "China East"; + public const string ChinaEast2 = "China East 2"; + public const string ChinaNorth = "China North"; + public const string ChinaNorth2 = "China North 2"; + public const string EastAsia = "East Asia"; + public const string EastUS = "East US"; + public const string EastUS2 = "East US 2"; + public const string EastUS2EUAP = "East US 2 EUAP"; + public const string EastUSSLV = "East US SLV"; + public const string FranceCentral = "France Central"; + public const string FranceSouth = "France South"; + public const string GermanyCentral = "Germany Central"; + public const string GermanyNorth = "Germany North"; + public const string GermanyNortheast = "Germany Northeast"; + public const string GermanyWestCentral = "Germany West Central"; + public const string JapanEast = "Japan East"; + public const string JapanWest = "Japan West"; + public const string JioIndiaCentral = "Jio India Central"; + public const string JioIndiaWest = "Jio India West"; + public const string KoreaCentral = "Korea Central"; + public const string KoreaSouth = "Korea South"; + public const string NorthCentralUS = "North Central US"; + public const string NorthEurope = "North Europe"; + public const string NorwayEast = "Norway East"; + public const string NorwayWest = "Norway West"; + public const string SouthAfricaNorth = "South Africa North"; + public const string SouthAfricaWest = "South Africa West"; + public const string SouthCentralUS = "South Central US"; + public const string SoutheastAsia = "Southeast Asia"; + public const string SouthIndia = "South India"; + public const string SwitzerlandNorth = "Switzerland North"; + public const string SwitzerlandWest = "Switzerland West"; + public const string UAECentral = "UAE Central"; + public const string UAENorth = "UAE North"; + public const string UKSouth = "UK South"; + public const string UKWest = "UK West"; + public const string USDoDCentral = "USDoD Central"; + public const string USDoDEast = "USDoD East"; + public const string USGovArizona = "USGov Arizona"; + public const string USGovTexas = "USGov Texas"; + public const string USGovVirginia = "USGov Virginia"; + public const string USNatEast = "USNat East"; + public const string USNatWest = "USNat West"; + public const string USSecEast = "USSec East"; + public const string USSecWest = "USSec West"; + public const string WestCentralUS = "West Central US"; + public const string WestEurope = "West Europe"; + public const string WestIndia = "West India"; + public const string WestUS = "West US"; + public const string WestUS2 = "West US 2"; + public const string WestUS3 = "West US 3"; + } + public abstract class RequestHandler + { + protected RequestHandler(); + public RequestHandler InnerHandler { get; set; } + public virtual Task SendAsync(RequestMessage request, CancellationToken cancellationToken); + } + public class RequestMessage : IDisposable + { + public RequestMessage(); + public RequestMessage(HttpMethod method, Uri requestUri); + public virtual Stream Content { get; set; } + public virtual Headers Headers { get; } + public virtual HttpMethod Method { get; } + public virtual Dictionary Properties { get; } + public virtual Uri RequestUri { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + } + public class RequestOptions + { + public RequestOptions(); + public string IfMatchEtag { get; set; } + public string IfNoneMatchEtag { get; set; } + public IReadOnlyDictionary Properties { get; set; } + } + public class ResponseMessage : IDisposable + { + public ResponseMessage(); + public ResponseMessage(HttpStatusCode statusCode, RequestMessage requestMessage=null, string errorMessage=null); + public virtual Stream Content { get; set; } + public virtual string ContinuationToken { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual RequestMessage RequestMessage { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual ResponseMessage EnsureSuccessStatusCode(); + } + public abstract class Response + { + protected Response(); + public abstract string ActivityId { get; } + public abstract CosmosDiagnostics Diagnostics { get; } + public abstract string ETag { get; } + public abstract Headers Headers { get; } + public abstract double RequestCharge { get; } + public abstract T Resource { get; } + public abstract HttpStatusCode StatusCode { get; } + public static implicit operator T (Response response); + } + public sealed class SpatialPath + { + public SpatialPath(); + public BoundingBoxProperties BoundingBox { get; set; } + public string Path { get; set; } + public Collection SpatialTypes { get; } + } + public enum SpatialType + { + LineString = 1, + MultiPolygon = 3, + Point = 0, + Polygon = 2, + } + public class ThroughputProperties + { + public Nullable AutoscaleMaxThroughput { get; } + public string ETag { get; } + public Nullable LastModified { get; } + public string SelfLink { get; } + public Nullable Throughput { get; } + public static ThroughputProperties CreateAutoscaleThroughput(int autoscaleMaxThroughput); + public static ThroughputProperties CreateManualThroughput(int throughput); + } + public class ThroughputResponse : Response + { + protected ThroughputResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public Nullable IsReplacePending { get; } + public Nullable MinThroughput { get; } + public override double RequestCharge { get; } + public override ThroughputProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator ThroughputProperties (ThroughputResponse response); + } + public abstract class TransactionalBatch + { + protected TransactionalBatch(); + public abstract TransactionalBatch CreateItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch CreateItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch DeleteItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract Task ExecuteAsync(TransactionalBatchRequestOptions requestOptions, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteAsync(CancellationToken cancellationToken=default(CancellationToken)); + public abstract TransactionalBatch ReadItem(string id, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItemStream(string id, Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch ReplaceItem(string id, T item, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItemStream(Stream streamPayload, TransactionalBatchItemRequestOptions requestOptions=null); + public abstract TransactionalBatch UpsertItem(T item, TransactionalBatchItemRequestOptions requestOptions=null); + } + public class TransactionalBatchItemRequestOptions : RequestOptions + { + public TransactionalBatchItemRequestOptions(); + public Nullable EnableContentResponseOnWrite { get; set; } + public Nullable IndexingDirective { get; set; } + } + public class TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual string ETag { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual Stream ResourceStream { get; } + public virtual TimeSpan RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + } + public class TransactionalBatchOperationResult : TransactionalBatchOperationResult + { + protected TransactionalBatchOperationResult(); + public virtual T Resource { get; set; } + } + public class TransactionalBatchRequestOptions : RequestOptions + { + public TransactionalBatchRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public string SessionToken { get; set; } + } + public class TransactionalBatchResponse : IDisposable, IEnumerable, IEnumerable, IReadOnlyCollection, IReadOnlyList + { + protected TransactionalBatchResponse(); + public virtual string ActivityId { get; } + public virtual int Count { get; } + public virtual CosmosDiagnostics Diagnostics { get; } + public virtual string ErrorMessage { get; } + public virtual Headers Headers { get; } + public virtual bool IsSuccessStatusCode { get; } + public virtual TransactionalBatchOperationResult this[int index] { get; } + public virtual double RequestCharge { get; } + public virtual Nullable RetryAfter { get; } + public virtual HttpStatusCode StatusCode { get; } + public void Dispose(); + protected virtual void Dispose(bool disposing); + public virtual IEnumerator GetEnumerator(); + public virtual TransactionalBatchOperationResult GetOperationResultAtIndex(int index); + IEnumerator System.Collections.IEnumerable.GetEnumerator(); + } + public class UniqueKey + { + public UniqueKey(); + public Collection Paths { get; } + } + public sealed class UniqueKeyPolicy + { + public UniqueKeyPolicy(); + public Collection UniqueKeys { get; } + } + public abstract class User + { + protected User(); + public abstract string Id { get; } + public abstract Task CreatePermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Permission GetPermission(string id); + public abstract FeedIterator GetPermissionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetPermissionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadAsync(RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceAsync(UserProperties userProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task UpsertPermissionAsync(PermissionProperties permissionProperties, Nullable tokenExpiryInSeconds=default(Nullable), RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class UserProperties + { + protected UserProperties(); + public UserProperties(string id); + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class UserResponse : Response + { + protected UserResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public virtual User User { get; } + public static implicit operator User (UserResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Fluent +{ + public class CompositeIndexDefinition + { + public T Attach(); + public CompositeIndexDefinition Path(string path); + public CompositeIndexDefinition Path(string path, CompositePathSortOrder sortOrder); + } + public class ConflictResolutionDefinition + { + public ContainerBuilder Attach(); + public ConflictResolutionDefinition WithCustomStoredProcedureResolution(string conflictResolutionProcedure); + public ConflictResolutionDefinition WithLastWriterWinsResolution(string conflictResolutionPath); + } + public class ContainerBuilder : ContainerDefinition + { + protected ContainerBuilder(); + public ContainerBuilder(Database database, string name, string partitionKeyPath); + public new ContainerProperties Build(); + public Task CreateAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(ThroughputProperties throughputProperties, CancellationToken cancellationToken=default(CancellationToken)); + public Task CreateIfNotExistsAsync(Nullable throughput=default(Nullable), CancellationToken cancellationToken=default(CancellationToken)); + public ConflictResolutionDefinition WithConflictResolution(); + public UniqueKeyDefinition WithUniqueKey(); + } + public abstract class ContainerDefinition where T : ContainerDefinition + { + public ContainerDefinition(); + public ContainerProperties Build(); + public T WithDefaultTimeToLive(int defaultTtlInSeconds); + public T WithDefaultTimeToLive(TimeSpan defaultTtlTimeSpan); + public IndexingPolicyDefinition WithIndexingPolicy(); + public T WithPartitionKeyDefinitionVersion(PartitionKeyDefinitionVersion partitionKeyDefinitionVersion); + public T WithTimeToLivePropertyPath(string propertyPath); + } + public class CosmosClientBuilder + { + public CosmosClientBuilder(string connectionString); + public CosmosClientBuilder(string accountEndpoint, string authKeyOrResourceToken); + public CosmosClientBuilder AddCustomHandlers(params RequestHandler[] customHandlers); + public CosmosClient Build(); + public CosmosClientBuilder WithApplicationName(string applicationName); + public CosmosClientBuilder WithApplicationPreferredRegions(IReadOnlyList applicationPreferredRegions); + public CosmosClientBuilder WithApplicationRegion(string applicationRegion); + public CosmosClientBuilder WithBulkExecution(bool enabled); + public CosmosClientBuilder WithConnectionModeDirect(); + public CosmosClientBuilder WithConnectionModeDirect(Nullable idleTcpConnectionTimeout=default(Nullable), Nullable openTcpConnectionTimeout=default(Nullable), Nullable maxRequestsPerTcpConnection=default(Nullable), Nullable maxTcpConnectionsPerEndpoint=default(Nullable), Nullable portReuseMode=default(Nullable), Nullable enableTcpConnectionEndpointRediscovery=default(Nullable)); + public CosmosClientBuilder WithConnectionModeGateway(Nullable maxConnectionLimit=default(Nullable), IWebProxy webProxy=null); + public CosmosClientBuilder WithConsistencyLevel(ConsistencyLevel consistencyLevel); + public CosmosClientBuilder WithContentResponseOnWrite(bool contentResponseOnWrite); + public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSerializer); + public CosmosClientBuilder WithHttpClientFactory(Func httpClientFactory); + public CosmosClientBuilder WithLimitToEndpoint(bool limitToEndpoint); + public CosmosClientBuilder WithRequestTimeout(TimeSpan requestTimeout); + public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions); + public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeOnThrottledRequests, int maxRetryAttemptsOnThrottledRequests); + } + public class IndexingPolicyDefinition + { + public IndexingPolicyDefinition(); + public T Attach(); + public IndexingPolicyDefinition WithAutomaticIndexing(bool enabled); + public CompositeIndexDefinition> WithCompositeIndex(); + public PathsDefinition> WithExcludedPaths(); + public PathsDefinition> WithIncludedPaths(); + public IndexingPolicyDefinition WithIndexingMode(IndexingMode indexingMode); + public SpatialIndexDefinition> WithSpatialIndex(); + } + public class PathsDefinition + { + public T Attach(); + public PathsDefinition Path(string path); + } + public class SpatialIndexDefinition + { + public T Attach(); + public SpatialIndexDefinition Path(string path); + public SpatialIndexDefinition Path(string path, params SpatialType[] spatialTypes); + } + public class UniqueKeyDefinition + { + public ContainerBuilder Attach(); + public UniqueKeyDefinition Path(string path); + } +} +namespace Microsoft.Azure.Cosmos.Linq +{ + public static class CosmosLinq + { + public static object InvokeUserDefinedFunction(string udfName, params object[] arguments); + } + public static class CosmosLinqExtensions + { + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> AverageAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> AverageAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> CountAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static bool IsDefined(this object obj); + public static bool IsNull(this object obj); + public static bool IsPrimitive(this object obj); + public static Task> MaxAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> MinAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task>> SumAsync(this IQueryable> source, CancellationToken cancellationToken=default(CancellationToken)); + public static Task> SumAsync(this IQueryable source, CancellationToken cancellationToken=default(CancellationToken)); + public static FeedIterator ToFeedIterator(this IQueryable query); + public static QueryDefinition ToQueryDefinition(this IQueryable query); + public static FeedIterator ToStreamIterator(this IQueryable query); + } +} +namespace Microsoft.Azure.Cosmos.Scripts +{ + public abstract class Scripts + { + protected Scripts(); + public abstract Task CreateStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task CreateUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task DeleteUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task> ExecuteStoredProcedureAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, PartitionKey partitionKey, dynamic parameters, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ExecuteStoredProcedureStreamAsync(string storedProcedureId, Stream streamPayload, PartitionKey partitionKey, StoredProcedureRequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract FeedIterator GetStoredProcedureQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetStoredProcedureQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetTriggerQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(QueryDefinition queryDefinition, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract FeedIterator GetUserDefinedFunctionQueryStreamIterator(string queryText=null, string continuationToken=null, QueryRequestOptions requestOptions=null); + public abstract Task ReadStoredProcedureAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadTriggerAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReadUserDefinedFunctionAsync(string id, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceStoredProcedureAsync(StoredProcedureProperties storedProcedureProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceTriggerAsync(TriggerProperties triggerProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + public abstract Task ReplaceUserDefinedFunctionAsync(UserDefinedFunctionProperties userDefinedFunctionProperties, RequestOptions requestOptions=null, CancellationToken cancellationToken=default(CancellationToken)); + } + public class StoredProcedureExecuteResponse : Response + { + protected StoredProcedureExecuteResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override T Resource { get; } + public virtual string ScriptLog { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + } + public class StoredProcedureProperties + { + public StoredProcedureProperties(); + public StoredProcedureProperties(string id, string body); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public Nullable LastModified { get; } + public string SelfLink { get; } + } + public class StoredProcedureRequestOptions : RequestOptions + { + public StoredProcedureRequestOptions(); + public Nullable ConsistencyLevel { get; set; } + public bool EnableScriptLogging { get; set; } + public string SessionToken { get; set; } + } + public class StoredProcedureResponse : Response + { + protected StoredProcedureResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override StoredProcedureProperties Resource { get; } + public virtual string SessionToken { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator StoredProcedureProperties (StoredProcedureResponse response); + } + public enum TriggerOperation : short + { + All = (short)0, + Create = (short)1, + Delete = (short)3, + Replace = (short)4, + Update = (short)2, + } + public class TriggerProperties + { + public TriggerProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + public TriggerOperation TriggerOperation { get; set; } + public TriggerType TriggerType { get; set; } + } + public class TriggerResponse : Response + { + protected TriggerResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override TriggerProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator TriggerProperties (TriggerResponse response); + } + public enum TriggerType : byte + { + Post = (byte)1, + Pre = (byte)0, + } + public class UserDefinedFunctionProperties + { + public UserDefinedFunctionProperties(); + public string Body { get; set; } + public string ETag { get; } + public string Id { get; set; } + public string SelfLink { get; } + } + public class UserDefinedFunctionResponse : Response + { + protected UserDefinedFunctionResponse(); + public override string ActivityId { get; } + public override CosmosDiagnostics Diagnostics { get; } + public override string ETag { get; } + public override Headers Headers { get; } + public override double RequestCharge { get; } + public override UserDefinedFunctionProperties Resource { get; } + public override HttpStatusCode StatusCode { get; } + public static implicit operator UserDefinedFunctionProperties (UserDefinedFunctionResponse response); + } +} +namespace Microsoft.Azure.Cosmos.Spatial +{ + public sealed class BoundingBox : IEquatable + { + public BoundingBox(Position min, Position max); + public Position Max { get; } + public Position Min { get; } + public bool Equals(BoundingBox other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public abstract class Crs + { + protected Crs(CrsType type); + public static Crs Default { get; } + public CrsType Type { get; } + public static Crs Unspecified { get; } + public static LinkedCrs Linked(string href); + public static LinkedCrs Linked(string href, string type); + public static NamedCrs Named(string name); + } + public enum CrsType + { + Linked = 1, + Named = 0, + Unspecified = 2, + } + public abstract class Geometry + { + protected Geometry(GeometryType type, GeometryParams geometryParams); + public IDictionary AdditionalProperties { get; } + public BoundingBox BoundingBox { get; } + public Crs Crs { get; } + public GeometryType Type { get; } + public double Distance(Geometry to); + public override bool Equals(object obj); + public override int GetHashCode(); + public bool Intersects(Geometry geometry2); + public bool IsValid(); + public GeometryValidationResult IsValidDetailed(); + public bool Within(Geometry outer); + } + public class GeometryParams + { + public GeometryParams(); + public IDictionary AdditionalProperties { get; set; } + public BoundingBox BoundingBox { get; set; } + public Crs Crs { get; set; } + } + public enum GeometryShape + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public enum GeometryType + { + GeometryCollection = 6, + LineString = 2, + MultiLineString = 3, + MultiPoint = 1, + MultiPolygon = 5, + Point = 0, + Polygon = 4, + } + public class GeometryValidationResult + { + public GeometryValidationResult(); + public bool IsValid { get; } + public string Reason { get; } + } + public sealed class LinearRing : IEquatable + { + public LinearRing(IList coordinates); + public ReadOnlyCollection Positions { get; } + public bool Equals(LinearRing other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LineString : Geometry, IEquatable + { + public LineString(IList coordinates); + public LineString(IList coordinates, GeometryParams geometryParams); + public ReadOnlyCollection Positions { get; } + public bool Equals(LineString other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class LinkedCrs : Crs, IEquatable + { + public string Href { get; } + public string HrefType { get; } + public bool Equals(LinkedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class MultiPolygon : Geometry, IEquatable + { + public MultiPolygon(IList polygons); + public MultiPolygon(IList polygons, GeometryParams geometryParams); + public ReadOnlyCollection Polygons { get; } + public bool Equals(MultiPolygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class NamedCrs : Crs, IEquatable + { + public string Name { get; } + public bool Equals(NamedCrs other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Point : Geometry, IEquatable + { + public Point(Position position); + public Point(Position position, GeometryParams geometryParams); + public Point(double longitude, double latitude); + public Position Position { get; } + public bool Equals(Point other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Polygon : Geometry, IEquatable + { + public Polygon(IList rings); + public Polygon(IList rings, GeometryParams geometryParams); + public Polygon(IList externalRingPositions); + public ReadOnlyCollection Rings { get; } + public bool Equals(Polygon other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class PolygonCoordinates : IEquatable + { + public PolygonCoordinates(IList rings); + public ReadOnlyCollection Rings { get; } + public bool Equals(PolygonCoordinates other); + public override bool Equals(object obj); + public override int GetHashCode(); + } + public sealed class Position : IEquatable + { + public Position(IList coordinates); + public Position(double longitude, double latitude); + public Position(double longitude, double latitude, Nullable altitude); + public Nullable Altitude { get; } + public ReadOnlyCollection Coordinates { get; } + public double Latitude { get; } + public double Longitude { get; } + public bool Equals(Position other); + public override bool Equals(object obj); + public override int GetHashCode(); + } +} diff --git a/Microsoft.Azure.Cosmos/src/Authorization/AuthorizationTokenProviderTokenCredential.cs b/Microsoft.Azure.Cosmos/src/Authorization/AuthorizationTokenProviderTokenCredential.cs index 0e7c0adb4b..dff20331f6 100644 --- a/Microsoft.Azure.Cosmos/src/Authorization/AuthorizationTokenProviderTokenCredential.cs +++ b/Microsoft.Azure.Cosmos/src/Authorization/AuthorizationTokenProviderTokenCredential.cs @@ -21,13 +21,11 @@ internal sealed class AuthorizationTokenProviderTokenCredential : AuthorizationT public AuthorizationTokenProviderTokenCredential( TokenCredential tokenCredential, Uri accountEndpoint, - TimeSpan requestTimeout, TimeSpan? backgroundTokenCredentialRefreshInterval) { this.tokenCredentialCache = new TokenCredentialCache( tokenCredential: tokenCredential, accountEndpoint: accountEndpoint, - requestTimeout: requestTimeout, backgroundTokenCredentialRefreshInterval: backgroundTokenCredentialRefreshInterval); } @@ -38,9 +36,12 @@ public AuthorizationTokenProviderTokenCredential( INameValueCollection headers, AuthorizationTokenType tokenType) { - string token = AuthorizationTokenProviderTokenCredential.GenerateAadAuthorizationSignature( - await this.tokenCredentialCache.GetTokenAsync(NoOpTrace.Singleton)); - return (token, default); + using (Trace trace = Trace.GetRootTrace(nameof(GetUserAuthorizationTokenAsync), TraceComponent.Authorization, TraceLevel.Info)) + { + string token = AuthorizationTokenProviderTokenCredential.GenerateAadAuthorizationSignature( + await this.tokenCredentialCache.GetTokenAsync(trace)); + return (token, default); + } } public override async ValueTask GetUserAuthorizationTokenAsync( @@ -61,10 +62,13 @@ public override async ValueTask AddAuthorizationHeaderAsync( string verb, AuthorizationTokenType tokenType) { - string token = AuthorizationTokenProviderTokenCredential.GenerateAadAuthorizationSignature( - await this.tokenCredentialCache.GetTokenAsync(NoOpTrace.Singleton)); + using (Trace trace = Trace.GetRootTrace(nameof(GetUserAuthorizationTokenAsync), TraceComponent.Authorization, TraceLevel.Info)) + { + string token = AuthorizationTokenProviderTokenCredential.GenerateAadAuthorizationSignature( + await this.tokenCredentialCache.GetTokenAsync(trace)); - headersCollection.Add(HttpConstants.HttpHeaders.Authorization, token); + headersCollection.Add(HttpConstants.HttpHeaders.Authorization, token); + } } public override void TraceUnauthorized( diff --git a/Microsoft.Azure.Cosmos/src/Authorization/TokenCredentialCache.cs b/Microsoft.Azure.Cosmos/src/Authorization/TokenCredentialCache.cs index 480d55984f..dd9a0cc0f1 100644 --- a/Microsoft.Azure.Cosmos/src/Authorization/TokenCredentialCache.cs +++ b/Microsoft.Azure.Cosmos/src/Authorization/TokenCredentialCache.cs @@ -1,6 +1,7 @@ //------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ +#nullable enable namespace Microsoft.Azure.Cosmos { using System; @@ -26,33 +27,35 @@ namespace Microsoft.Azure.Cosmos internal sealed class TokenCredentialCache : IDisposable { // Default token expiration time is 1hr. - // Making the default 25% of the token life span. This gives 75% of the tokens life for transient error + // Making the default 50% of the token life span. This gives 50% of the tokens life for transient error // to get resolved before the token expires. - public static readonly double DefaultBackgroundTokenCredentialRefreshIntervalPercentage = .25; + public static readonly double DefaultBackgroundTokenCredentialRefreshIntervalPercentage = .50; // The maximum time a task delayed is allowed is Int32.MaxValue in Milliseconds which is roughly 24 days public static readonly TimeSpan MaxBackgroundRefreshInterval = TimeSpan.FromMilliseconds(Int32.MaxValue); + // The token refresh retries half the time. Given default of 1hr it will retry at 30m, 15, 7.5, 3.75, 1.875 + // If the background refresh fails with less than a minute then just allow the request to hit the exception. + public static readonly TimeSpan MinimumTimeBetweenBackgroundRefreshInterval = TimeSpan.FromMinutes(1); + private const string ScopeFormat = "https://{0}/.default"; private readonly TokenRequestContext tokenRequestContext; private readonly TokenCredential tokenCredential; private readonly CancellationTokenSource cancellationTokenSource; private readonly CancellationToken cancellationToken; private readonly TimeSpan? userDefinedBackgroundTokenCredentialRefreshInterval; - private readonly TimeSpan requestTimeout; - private readonly SemaphoreSlim getTokenRefreshLock = new SemaphoreSlim(1); - private readonly SemaphoreSlim backgroundRefreshLock = new SemaphoreSlim(1); + private readonly SemaphoreSlim isTokenRefreshingLock = new SemaphoreSlim(1); + private readonly object backgroundRefreshLock = new object(); private TimeSpan? systemBackgroundTokenCredentialRefreshInterval; - private AccessToken cachedAccessToken; + private AccessToken? cachedAccessToken; private bool isBackgroundTaskRunning = false; private bool isDisposed = false; internal TokenCredentialCache( TokenCredential tokenCredential, Uri accountEndpoint, - TimeSpan requestTimeout, TimeSpan? backgroundTokenCredentialRefreshInterval) { this.tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential)); @@ -67,11 +70,6 @@ internal TokenCredentialCache( string.Format(TokenCredentialCache.ScopeFormat, accountEndpoint.Host) }); - if (requestTimeout <= TimeSpan.Zero) - { - throw new ArgumentException($"{nameof(requestTimeout)} must be a positive value greater than 0. Value '{requestTimeout.TotalMilliseconds.ToString(CultureInfo.InvariantCulture)}'Milliseconds."); - } - if (backgroundTokenCredentialRefreshInterval.HasValue) { if (backgroundTokenCredentialRefreshInterval.Value <= TimeSpan.Zero) @@ -88,7 +86,6 @@ internal TokenCredentialCache( } this.userDefinedBackgroundTokenCredentialRefreshInterval = backgroundTokenCredentialRefreshInterval; - this.requestTimeout = requestTimeout; this.cancellationTokenSource = new CancellationTokenSource(); this.cancellationToken = this.cancellationTokenSource.Token; } @@ -103,26 +100,16 @@ internal async ValueTask GetTokenAsync(ITrace trace) throw new ObjectDisposedException("TokenCredentialCache"); } - if (this.cachedAccessToken.ExpiresOn <= DateTime.UtcNow) + // Use the cached token if it is still valid + if (this.cachedAccessToken.HasValue && + DateTime.UtcNow < this.cachedAccessToken.Value.ExpiresOn) { - await this.getTokenRefreshLock.WaitAsync(); - - // Don't refresh if another thread already updated it. - if (this.cachedAccessToken.ExpiresOn <= DateTime.UtcNow) - { - try - { - await this.RefreshCachedTokenWithRetryHelperAsync(trace); - this.StartRefreshToken(); - } - finally - { - this.getTokenRefreshLock.Release(); - } - } + return this.cachedAccessToken.Value.Token; } - return this.cachedAccessToken.Token; + AccessToken accessToken = await this.RefreshCachedTokenWithRetryHelperAsync(trace); + this.StartBackgroundTokenRefreshLoop(); + return accessToken.Token; } public void Dispose() @@ -137,139 +124,142 @@ public void Dispose() this.isDisposed = true; } - private async ValueTask RefreshCachedTokenWithRetryHelperAsync(ITrace trace) + private async ValueTask RefreshCachedTokenWithRetryHelperAsync( + ITrace trace) { - // A different thread is already updating the access token. Count starts off at 1. - bool skipRefreshBecause = this.backgroundRefreshLock.CurrentCount != 1; - await this.backgroundRefreshLock.WaitAsync(); - try + Exception? lastException = null; + const int totalRetryCount = 2; + for (int retry = 0; retry < totalRetryCount; retry++) { - // Token was already refreshed successfully from another thread. - if (skipRefreshBecause && this.cachedAccessToken.ExpiresOn > DateTime.UtcNow) + if (this.cancellationToken.IsCancellationRequested) { - return; + DefaultTrace.TraceInformation( + "Stop RefreshTokenWithIndefiniteRetries because cancellation is requested"); + + break; } - Exception lastException = null; - const int totalRetryCount = 3; - for (int retry = 0; retry < totalRetryCount; retry++) + using (ITrace getTokenTrace = trace.StartChild( + name: nameof(this.RefreshCachedTokenWithRetryHelperAsync), + component: TraceComponent.Authorization, + level: Tracing.TraceLevel.Info)) { - if (this.cancellationToken.IsCancellationRequested) + try { - DefaultTrace.TraceInformation( - "Stop RefreshTokenWithIndefiniteRetries because cancellation is requested"); - - break; + return await this.UpdateCachedTokenAsync(); } - - using (ITrace getTokenTrace = trace.StartChild( - name: nameof(this.RefreshCachedTokenWithRetryHelperAsync), - component: TraceComponent.Authorization, - level: Tracing.TraceLevel.Info)) + catch (RequestFailedException requestFailedException) { - try - { - await this.ExecuteGetTokenWithRequestTimeoutAsync(); - return; - } - catch (RequestFailedException requestFailedException) - { - lastException = requestFailedException; - getTokenTrace.AddDatum( - $"RequestFailedException at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}", - requestFailedException); + lastException = requestFailedException; + getTokenTrace.AddDatum( + $"RequestFailedException at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}", + requestFailedException); - DefaultTrace.TraceError($"TokenCredential.GetToken() failed with RequestFailedException. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); + DefaultTrace.TraceError($"TokenCredential.GetToken() failed with RequestFailedException. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); - // Don't retry on auth failures - if (requestFailedException.Status == (int)HttpStatusCode.Unauthorized || - requestFailedException.Status == (int)HttpStatusCode.Forbidden) - { - this.cachedAccessToken = default; - throw; - } - } - catch (OperationCanceledException operationCancelled) + // Don't retry on auth failures + if (requestFailedException.Status == (int)HttpStatusCode.Unauthorized || + requestFailedException.Status == (int)HttpStatusCode.Forbidden) { - lastException = operationCancelled; - getTokenTrace.AddDatum( - $"OperationCanceledException at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}", - operationCancelled); - - DefaultTrace.TraceError( - $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); - - throw CosmosExceptionFactory.CreateRequestTimeoutException( - message: ClientResources.FailedToGetAadToken, - headers: new Headers() - { - SubStatusCode = SubStatusCodes.FailedToGetAadToken, - }, - innerException: lastException, - trace: getTokenTrace); - } - catch (Exception exception) - { - lastException = exception; - getTokenTrace.AddDatum( - $"Exception at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}", - exception); - - DefaultTrace.TraceError( - $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); + this.cachedAccessToken = default; + throw; } } + catch (OperationCanceledException operationCancelled) + { + lastException = operationCancelled; + getTokenTrace.AddDatum( + $"OperationCanceledException at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}", + operationCancelled); - DefaultTrace.TraceError( - $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); - } + DefaultTrace.TraceError( + $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); - throw CosmosExceptionFactory.CreateUnauthorizedException( - message: ClientResources.FailedToGetAadToken, - headers: new Headers() + throw CosmosExceptionFactory.CreateRequestTimeoutException( + message: ClientResources.FailedToGetAadToken, + headers: new Headers() + { + SubStatusCode = SubStatusCodes.FailedToGetAadToken, + }, + innerException: lastException, + trace: getTokenTrace); + } + catch (Exception exception) { - SubStatusCode = SubStatusCodes.FailedToGetAadToken, - }, - innerException: lastException, - trace: trace); + lastException = exception; + getTokenTrace.AddDatum( + $"Exception at {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}", + exception); + + DefaultTrace.TraceError( + $"TokenCredential.GetTokenAsync() failed. scope = {string.Join(";", this.tokenRequestContext.Scopes)}, retry = {retry}, Exception = {lastException}"); + } + } } - finally + + if (lastException == null) { - this.backgroundRefreshLock.Release(); + throw new ArgumentException("Last exception is null."); } + + // The retries have been exhausted. Throw the last exception. + throw lastException; } - private async ValueTask ExecuteGetTokenWithRequestTimeoutAsync() + /// + /// This method takes a lock to only allow one thread to update the token + /// at a time. If the token was updated while it was waiting for the lock it + /// returns the new cached token. + /// + private async Task UpdateCachedTokenAsync() { - using CancellationTokenSource singleRequestCancellationTokenSource = new CancellationTokenSource(this.requestTimeout); - - Task[] valueTasks = new Task[2]; - valueTasks[0] = Task.Delay(this.requestTimeout); - - Task valueTaskTokenCredential = this.tokenCredential.GetTokenAsync( - this.tokenRequestContext, - singleRequestCancellationTokenSource.Token).AsTask(); + DateTimeOffset? initialExpireTime = this.cachedAccessToken?.ExpiresOn; - valueTasks[1] = valueTaskTokenCredential; - await Task.WhenAny(valueTasks); + await this.isTokenRefreshingLock.WaitAsync(); - // Time out completed and the GetTokenAsync did not - if (valueTasks[0].IsCompleted && !valueTasks[1].IsCompleted) + // Token was already refreshed successfully from another thread. + if (this.cachedAccessToken.HasValue && + (!initialExpireTime.HasValue || this.cachedAccessToken.Value.ExpiresOn != initialExpireTime.Value)) { - throw new OperationCanceledException($"TokenCredential.GetTokenAsync request timed out after {this.requestTimeout}"); + return this.cachedAccessToken.Value; } - this.cachedAccessToken = await valueTaskTokenCredential; + try + { + this.cachedAccessToken = await this.tokenCredential.GetTokenAsync( + requestContext: this.tokenRequestContext, + cancellationToken: default); + + if (!this.cachedAccessToken.HasValue) + { + throw new ArgumentNullException("TokenCredential.GetTokenAsync returned a null token."); + } + + if (this.cachedAccessToken.Value.ExpiresOn < DateTimeOffset.UtcNow) + { + throw new ArgumentOutOfRangeException($"TokenCredential.GetTokenAsync returned a token that is already expired. Current Time:{DateTime.UtcNow:O}; Token expire time:{this.cachedAccessToken.Value.ExpiresOn:O}"); + } - if (!this.userDefinedBackgroundTokenCredentialRefreshInterval.HasValue) + if (!this.userDefinedBackgroundTokenCredentialRefreshInterval.HasValue) + { + double refreshIntervalInSeconds = (this.cachedAccessToken.Value.ExpiresOn - DateTimeOffset.UtcNow).TotalSeconds * DefaultBackgroundTokenCredentialRefreshIntervalPercentage; + + // Ensure the background refresh interval is a valid range. + refreshIntervalInSeconds = Math.Max(refreshIntervalInSeconds, TokenCredentialCache.MinimumTimeBetweenBackgroundRefreshInterval.TotalSeconds); + refreshIntervalInSeconds = Math.Min(refreshIntervalInSeconds, TokenCredentialCache.MaxBackgroundRefreshInterval.TotalSeconds); + this.systemBackgroundTokenCredentialRefreshInterval = TimeSpan.FromSeconds(refreshIntervalInSeconds); + } + + return this.cachedAccessToken.Value; + } + finally { - double totalSecondUntilExpire = (this.cachedAccessToken.ExpiresOn - DateTimeOffset.UtcNow).TotalSeconds * DefaultBackgroundTokenCredentialRefreshIntervalPercentage; - this.systemBackgroundTokenCredentialRefreshInterval = TimeSpan.FromSeconds(totalSecondUntilExpire); + this.isTokenRefreshingLock.Release(); } } #pragma warning disable VSTHRD100 // Avoid async void methods - private async void StartRefreshToken() + private async void StartBackgroundTokenRefreshLoop() #pragma warning restore VSTHRD100 // Avoid async void methods { if (this.isBackgroundTaskRunning) @@ -277,7 +267,16 @@ private async void StartRefreshToken() return; } - this.isBackgroundTaskRunning = true; + lock (this.backgroundRefreshLock) + { + if (this.isBackgroundTaskRunning) + { + return; + } + + this.isBackgroundTaskRunning = true; + } + while (!this.cancellationTokenSource.IsCancellationRequested) { try @@ -291,7 +290,7 @@ private async void StartRefreshToken() if (this.BackgroundTokenCredentialRefreshInterval.Value > TokenCredentialCache.MaxBackgroundRefreshInterval) { DefaultTrace.TraceWarning( - "StartRefreshToken() Stopped - The BackgroundTokenCredentialRefreshInterval is {0} which is greater than the maximum allow.", + "BackgroundTokenRefreshLoop() Stopped - The BackgroundTokenCredentialRefreshInterval is {0} which is greater than the maximum allow.", this.BackgroundTokenCredentialRefreshInterval.Value); return; @@ -299,9 +298,9 @@ private async void StartRefreshToken() await Task.Delay(this.BackgroundTokenCredentialRefreshInterval.Value, this.cancellationToken); - DefaultTrace.TraceInformation("StartRefreshToken() - Invoking refresh"); + DefaultTrace.TraceInformation("BackgroundTokenRefreshLoop() - Invoking refresh"); - await this.RefreshCachedTokenWithRetryHelperAsync(NoOpTrace.Singleton); + await this.UpdateCachedTokenAsync(); } catch (Exception ex) { @@ -312,8 +311,27 @@ private async void StartRefreshToken() } DefaultTrace.TraceWarning( - "StartRefreshToken() - Unable to refresh token credential cache. Exception: {0}", + "BackgroundTokenRefreshLoop() - Unable to refresh token credential cache. Exception: {0}", ex.ToString()); + + // Since it failed retry again in with half the token life span again. + if (!this.userDefinedBackgroundTokenCredentialRefreshInterval.HasValue && this.cachedAccessToken.HasValue) + { + double totalSecondUntilExpire = (this.cachedAccessToken.Value.ExpiresOn - DateTimeOffset.UtcNow).TotalSeconds * DefaultBackgroundTokenCredentialRefreshIntervalPercentage; + this.systemBackgroundTokenCredentialRefreshInterval = TimeSpan.FromSeconds(totalSecondUntilExpire); + + // Refresh interval is less than the minimum. Stop the background refresh. + // The background refresh will start again on the next successful token refresh. + if (this.systemBackgroundTokenCredentialRefreshInterval < TokenCredentialCache.MinimumTimeBetweenBackgroundRefreshInterval) + { + lock (this.backgroundRefreshLock) + { + this.isBackgroundTaskRunning = false; + } + + return; + } + } } } } diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs index b2afc0b60f..987264f6bb 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncBatcher.cs @@ -113,11 +113,10 @@ public virtual async Task DispatchAsync( BatchPartitionMetric partitionMetric, CancellationToken cancellationToken = default) { - await this.clientContext.OperationHelperAsync("Batch Dispatch Async", - requestOptions: null, - task: (trace) => this.DispatchHelperAsync(trace, partitionMetric, cancellationToken), - traceComponent: TraceComponent.Batch, - traceLevel: Tracing.TraceLevel.Info); + using (ITrace trace = Tracing.Trace.GetRootTrace("Batch Dispatch Async", TraceComponent.Batch, Tracing.TraceLevel.Info)) + { + await this.DispatchHelperAsync(trace, partitionMetric, cancellationToken); + } } private async Task DispatchHelperAsync( @@ -141,7 +140,7 @@ private async Task DispatchHelperAsync( // Any overflow goes to a new batch foreach (ItemBatchOperation operation in pendingOperations) { - await this.retrier(operation, trace, cancellationToken); + await this.retrier(operation, cancellationToken); } } catch (Exception ex) @@ -175,10 +174,10 @@ private async Task DispatchHelperAsync( if (!response.IsSuccessStatusCode) { - Documents.ShouldRetryResult shouldRetry = await itemBatchOperation.Context.ShouldRetryAsync(response, cancellationToken); + ShouldRetryResult shouldRetry = await itemBatchOperation.Context.ShouldRetryAsync(response, cancellationToken); if (shouldRetry.ShouldRetry) { - await this.retrier(itemBatchOperation, trace, cancellationToken); + await this.retrier(itemBatchOperation, cancellationToken); continue; } } @@ -244,6 +243,5 @@ internal delegate Task BatchAsyncBatcherE /// An instance of . internal delegate Task BatchAsyncBatcherRetryDelegate( ItemBatchOperation operation, - ITrace trace, CancellationToken cancellationToken); } diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncContainerExecutor.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncContainerExecutor.cs index 9c8122c70d..ce4f8ccfe2 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncContainerExecutor.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncContainerExecutor.cs @@ -72,6 +72,7 @@ public BatchAsyncContainerExecutor( public virtual async Task AddAsync( ItemBatchOperation operation, + ITrace trace, ItemRequestOptions itemRequestOptions = null, CancellationToken cancellationToken = default) { @@ -82,9 +83,15 @@ public virtual async Task AddAsync( await this.ValidateOperationAsync(operation, itemRequestOptions, cancellationToken); - string resolvedPartitionKeyRangeId = await this.ResolvePartitionKeyRangeIdAsync(operation, cancellationToken).ConfigureAwait(false); + string resolvedPartitionKeyRangeId = await this.ResolvePartitionKeyRangeIdAsync( + operation, + trace, + cancellationToken).ConfigureAwait(false); BatchAsyncStreamer streamer = this.GetOrAddStreamerForPartitionKeyRange(resolvedPartitionKeyRangeId); - ItemBatchOperationContext context = new ItemBatchOperationContext(resolvedPartitionKeyRangeId, BatchAsyncContainerExecutor.GetRetryPolicy(this.cosmosContainer, operation.OperationType, this.retryOptions)); + ItemBatchOperationContext context = new ItemBatchOperationContext( + resolvedPartitionKeyRangeId, + trace, + BatchAsyncContainerExecutor.GetRetryPolicy(this.cosmosContainer, operation.OperationType, this.retryOptions)); operation.AttachContext(context); streamer.Add(operation); return await context.OperationTask; @@ -178,13 +185,12 @@ private static void AddHeadersToRequestMessage(RequestMessage requestMessage, st private async Task ReBatchAsync( ItemBatchOperation operation, - ITrace trace, CancellationToken cancellationToken) { - using (ITrace retryTrace = trace.StartChild("Batch Retry Async", TraceComponent.Batch, Tracing.TraceLevel.Info)) + using (ITrace trace = Tracing.Trace.GetRootTrace("Batch Retry Async", TraceComponent.Batch, Tracing.TraceLevel.Verbose)) { - string resolvedPartitionKeyRangeId = await this.ResolvePartitionKeyRangeIdAsync(operation, cancellationToken).ConfigureAwait(false); - operation.Context.ReRouteOperation(resolvedPartitionKeyRangeId); + string resolvedPartitionKeyRangeId = await this.ResolvePartitionKeyRangeIdAsync(operation, trace, cancellationToken).ConfigureAwait(false); + operation.Context.ReRouteOperation(resolvedPartitionKeyRangeId, trace); BatchAsyncStreamer streamer = this.GetOrAddStreamerForPartitionKeyRange(resolvedPartitionKeyRangeId); streamer.Add(operation); } @@ -192,13 +198,18 @@ private async Task ReBatchAsync( private async Task ResolvePartitionKeyRangeIdAsync( ItemBatchOperation operation, + ITrace trace, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - PartitionKeyDefinition partitionKeyDefinition = await this.cosmosContainer.GetPartitionKeyDefinitionAsync(cancellationToken); + ContainerProperties cachedContainerPropertiesAsync = await this.cosmosContainer.GetCachedContainerPropertiesAsync( + forceRefresh: false, + trace: trace, + cancellationToken: cancellationToken); + PartitionKeyDefinition partitionKeyDefinition = cachedContainerPropertiesAsync?.PartitionKey; CollectionRoutingMap collectionRoutingMap = await this.cosmosContainer.GetRoutingMapAsync(cancellationToken); - Debug.Assert(operation.RequestOptions?.Properties?.TryGetValue(WFConstants.BackendHeaders.EffectivePartitionKeyString, out object epkObj) == null, "EPK is not supported"); + Debug.Assert(operation.RequestOptions?.Properties?.TryGetValue(WFConstants.BackendHeaders.EffectivePartitionKeyString, out object _) == null, "EPK is not supported"); Documents.Routing.PartitionKeyInternal partitionKeyInternal = await this.GetPartitionKeyInternalAsync(operation, cancellationToken); operation.PartitionKeyJson = partitionKeyInternal.ToJsonString(); string effectivePartitionKeyString = partitionKeyInternal.GetEffectivePartitionKeyString(partitionKeyDefinition); diff --git a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncStreamer.cs b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncStreamer.cs index 26170262b6..097c0cff4a 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncStreamer.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/BatchAsyncStreamer.cs @@ -153,13 +153,16 @@ public void Dispose() private void ResetTimer() { this.currentTimer = this.timerWheel.CreateTimer(BatchAsyncStreamer.batchTimeout); - this.timerTask = this.currentTimer.StartTimerAsync().ContinueWith((task) => + this.timerTask = this.GetTimerTaskAsync(); + } + + private async Task GetTimerTaskAsync() + { + await this.currentTimer.StartTimerAsync(); + if (!this.cancellationTokenSource.IsCancellationRequested) { - if (task.IsCompleted) - { - this.DispatchTimer(); - } - }, this.cancellationTokenSource.Token); + this.DispatchTimer(); + } } private void StartCongestionControlTimer() diff --git a/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs b/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs index eabf539789..e4316eace4 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/ItemBatchOperationContext.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; + using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; /// @@ -23,31 +24,46 @@ internal class ItemBatchOperationContext : IDisposable private readonly IDocumentClientRetryPolicy retryPolicy; - private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + private readonly ITrace initialTrace; public ItemBatchOperationContext( string partitionKeyRangeId, + ITrace trace, IDocumentClientRetryPolicy retryPolicy = null) { - this.PartitionKeyRangeId = partitionKeyRangeId; + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + this.PartitionKeyRangeId = partitionKeyRangeId ?? throw new ArgumentNullException(nameof(partitionKeyRangeId)); + this.initialTrace = trace; this.retryPolicy = retryPolicy; } /// /// Based on the Retry Policy, if a failed response should retry. /// - public Task ShouldRetryAsync( + public async Task ShouldRetryAsync( TransactionalBatchOperationResult batchOperationResult, CancellationToken cancellationToken) { if (this.retryPolicy == null || batchOperationResult.IsSuccessStatusCode) { - return Task.FromResult(ShouldRetryResult.NoRetry()); + return ShouldRetryResult.NoRetry(); } ResponseMessage responseMessage = batchOperationResult.ToResponseMessage(); - return this.retryPolicy.ShouldRetryAsync(responseMessage, cancellationToken); + ShouldRetryResult shouldRetry = await this.retryPolicy.ShouldRetryAsync(responseMessage, cancellationToken); + if (shouldRetry.ShouldRetry) + { + this.initialTrace.AddChild(batchOperationResult.Trace); + } + + return shouldRetry; } public void Complete( @@ -56,6 +72,8 @@ public void Complete( { if (this.AssertBatcher(completer)) { + this.initialTrace.AddChild(result.Trace); + result.Trace = this.initialTrace; this.taskCompletionSource.SetResult(result); } @@ -74,9 +92,12 @@ public void Fail( this.Dispose(); } - public void ReRouteOperation(string newPartitionKeyRangeId) + public void ReRouteOperation( + string newPartitionKeyRangeId, + ITrace trace) { this.PartitionKeyRangeId = newPartitionKeyRangeId; + this.initialTrace.AddChild(trace); } public void Dispose() diff --git a/Microsoft.Azure.Cosmos/src/BulkExecutionRetryPolicy.cs b/Microsoft.Azure.Cosmos/src/BulkExecutionRetryPolicy.cs index 16d21e49d8..474cf6dd8d 100644 --- a/Microsoft.Azure.Cosmos/src/BulkExecutionRetryPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/BulkExecutionRetryPolicy.cs @@ -19,6 +19,7 @@ namespace Microsoft.Azure.Cosmos /// internal sealed class BulkExecutionRetryPolicy : IDocumentClientRetryPolicy { + private const int SubstatusCodeBatchResponseSizeExceeded = 3402; private const int MaxRetryOn410 = 10; private readonly IDocumentClientRetryPolicy nextRetryPolicy; private readonly OperationType operationType; @@ -86,8 +87,6 @@ public void OnBeforeSendRequest(DocumentServiceRequest request) this.nextRetryPolicy.OnBeforeSendRequest(request); } - private bool IsReadRequest => this.operationType == OperationType.Read; - private async Task ShouldRetryInternalAsync( HttpStatusCode? statusCode, SubStatusCodes? subStatusCode, @@ -126,9 +125,9 @@ await partitionKeyRangeCache.TryGetOverlappingRangesAsync( } // Batch API can return 413 which means the response is bigger than 4Mb. - // Operations that exceed the 4Mb limit are returned as 413, while the operations within the 4Mb limit will be 200 - if (this.IsReadRequest - && statusCode == HttpStatusCode.RequestEntityTooLarge) + // Operations that exceed the 4Mb limit are returned as 413/3402, while the operations within the 4Mb limit will be 200 + if (statusCode == HttpStatusCode.RequestEntityTooLarge + && (int)subStatusCode == SubstatusCodeBatchResponseSizeExceeded) { return ShouldRetryResult.RetryAfter(TimeSpan.Zero); } diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs index d8bc0b633e..7e65fb7a55 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/ChangeFeedIteratorCore.cs @@ -235,6 +235,8 @@ private async Task ReadNextInternalAsync(ITrace trace, Cancella trace, out CosmosException cosmosException)) { + // Initialization issue, there are no enumerators to invoke + this.hasMoreResults = false; throw createException; } @@ -247,9 +249,18 @@ private async Task ReadNextInternalAsync(ITrace trace, Cancella } CrossPartitionChangeFeedAsyncEnumerator enumerator = monadicEnumerator.Result; - if (!await enumerator.MoveNextAsync(trace)) + enumerator.SetCancellationToken(cancellationToken); + + try { - throw new InvalidOperationException("ChangeFeed enumerator should always have a next continuation"); + if (!await enumerator.MoveNextAsync(trace)) + { + throw new InvalidOperationException("ChangeFeed enumerator should always have a next continuation"); + } + } + catch (OperationCanceledException ex) when (!(ex is CosmosOperationCanceledException)) + { + throw new CosmosOperationCanceledException(ex, trace); } if (enumerator.Current.Failed) @@ -259,7 +270,7 @@ private async Task ReadNextInternalAsync(ITrace trace, Cancella trace, out CosmosException cosmosException)) { - throw enumerator.Current.Exception; + throw ExceptionWithStackTraceException.UnWrapMonadExcepion(enumerator.Current.Exception, trace); } if (!IsRetriableException(cosmosException)) diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeed/Pagination/CrossPartitionChangeFeedAsyncEnumerator.cs b/Microsoft.Azure.Cosmos/src/ChangeFeed/Pagination/CrossPartitionChangeFeedAsyncEnumerator.cs index f681e922af..b04d8ba792 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeed/Pagination/CrossPartitionChangeFeedAsyncEnumerator.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeed/Pagination/CrossPartitionChangeFeedAsyncEnumerator.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed.Pagination internal sealed class CrossPartitionChangeFeedAsyncEnumerator : IAsyncEnumerator>> { private readonly CrossPartitionRangePageAsyncEnumerator crossPartitionEnumerator; - private readonly CancellationToken cancellationToken; + private CancellationToken cancellationToken; private TryCatch>? bufferedException; private CrossPartitionChangeFeedAsyncEnumerator( @@ -133,6 +133,12 @@ public async ValueTask MoveNextAsync(ITrace trace) } } + public void SetCancellationToken(CancellationToken cancellationToken) + { + this.cancellationToken = cancellationToken; + this.crossPartitionEnumerator.SetCancellationToken(cancellationToken); + } + public static CrossPartitionChangeFeedAsyncEnumerator Create( IDocumentContainer documentContainer, CrossFeedRangeState state, diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs index 7bf3044522..712617c369 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/FeedManagement/PartitionControllerCore.cs @@ -45,7 +45,7 @@ public override async Task InitializeAsync() public override async Task AddOrUpdateLeaseAsync(DocumentServiceLease lease) { - TaskCompletionSource tcs = new TaskCompletionSource(); + TaskCompletionSource tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (!this.currentlyOwnedPartitions.TryAdd(lease.CurrentLeaseToken, tcs)) { diff --git a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/CosmosContainerExtensions.cs b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/CosmosContainerExtensions.cs index f2d737e77d..6d8b326a05 100644 --- a/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/CosmosContainerExtensions.cs +++ b/Microsoft.Azure.Cosmos/src/ChangeFeedProcessor/Utils/CosmosContainerExtensions.cs @@ -14,6 +14,11 @@ namespace Microsoft.Azure.Cosmos.ChangeFeed.Utils internal static class CosmosContainerExtensions { + private static readonly ItemRequestOptions itemRequestOptionsWithResponseEnabled = new ItemRequestOptions() + { + EnableContentResponseOnWrite = false + }; + public static readonly CosmosSerializerCore DefaultJsonSerializer = new CosmosSerializerCore(); public static async Task TryGetItemAsync( @@ -38,7 +43,7 @@ public static async Task> TryCreateItemAsync( { using (Stream itemStream = CosmosContainerExtensions.DefaultJsonSerializer.ToStream(item)) { - using (ResponseMessage response = await container.CreateItemStreamAsync(itemStream, partitionKey).ConfigureAwait(false)) + using (ResponseMessage response = await container.CreateItemStreamAsync(itemStream, partitionKey, itemRequestOptionsWithResponseEnabled).ConfigureAwait(false)) { if (response.StatusCode == HttpStatusCode.Conflict) { @@ -51,7 +56,7 @@ public static async Task> TryCreateItemAsync( return new ItemResponse( response.StatusCode, response.Headers, - CosmosContainerExtensions.DefaultJsonSerializer.FromStream(response.Content), + item, response.Trace); } } @@ -66,13 +71,14 @@ public static async Task> TryReplaceItemAsync( { using (Stream itemStream = CosmosContainerExtensions.DefaultJsonSerializer.ToStream(item)) { + itemRequestOptions.EnableContentResponseOnWrite = false; using (ResponseMessage response = await container.ReplaceItemStreamAsync(itemStream, itemId, partitionKey, itemRequestOptions).ConfigureAwait(false)) { response.EnsureSuccessStatusCode(); return new ItemResponse( response.StatusCode, - response.Headers, - CosmosContainerExtensions.DefaultJsonSerializer.FromStream(response.Content), + response.Headers, + item, response.Trace); } } @@ -84,6 +90,8 @@ public static async Task TryDeleteItemAsync( string itemId, ItemRequestOptions cosmosItemRequestOptions = null) { + cosmosItemRequestOptions ??= new ItemRequestOptions(); + cosmosItemRequestOptions.EnableContentResponseOnWrite = false; using (ResponseMessage response = await container.DeleteItemStreamAsync(itemId, partitionKey, cosmosItemRequestOptions).ConfigureAwait(false)) { return response.IsSuccessStatusCode; diff --git a/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs b/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs index 35d7025382..4d5d352a41 100644 --- a/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/ClientRetryPolicy.cs @@ -71,9 +71,15 @@ public async Task ShouldRetryAsync( // Received Connection error (HttpRequestException), initiate the endpoint rediscovery if (exception is HttpRequestException _) { - DefaultTrace.TraceWarning("Endpoint not reachable. Refresh cache and retry"); + DefaultTrace.TraceWarning("ClientRetryPolicy: Gateway HttpRequestException Endpoint not reachable. Failed Location: {0}; ResourceAddress: {1}", + this.documentServiceRequest?.RequestContext?.LocationEndpointToRoute?.ToString() ?? string.Empty, + this.documentServiceRequest?.ResourceAddress ?? string.Empty); + + // Mark both read and write requests because it gateway exception. + // This means all requests going to the region will fail. return await this.ShouldRetryOnEndpointFailureAsync( isReadRequest: this.isReadRequest, + markBothReadAndWriteAsUnavailable: true, forceRefresh: false, retryOnPreferredLocations: true); } @@ -152,19 +158,36 @@ private async Task ShouldRetryInternalAsync( return null; } + // Received request timeout + if (statusCode == HttpStatusCode.RequestTimeout) + { + DefaultTrace.TraceWarning("ClientRetryPolicy: RequestTimeout. Failed Location: {0}; ResourceAddress: {1}", + this.documentServiceRequest?.RequestContext?.LocationEndpointToRoute?.ToString() ?? string.Empty, + this.documentServiceRequest?.ResourceAddress ?? string.Empty); + + // Mark the partition key range as unavailable to retry future request on a new region. + this.partitionKeyRangeLocationCache.TryMarkEndpointUnavailableForPartitionKeyRange( + this.documentServiceRequest); + } + // Received 403.3 on write region, initiate the endpoint rediscovery if (statusCode == HttpStatusCode.Forbidden && subStatusCode == SubStatusCodes.WriteForbidden) { + // It's a write forbidden so it safe to retry if (this.partitionKeyRangeLocationCache.TryMarkEndpointUnavailableForPartitionKeyRange( this.documentServiceRequest)) { return ShouldRetryResult.RetryAfter(TimeSpan.Zero); } - DefaultTrace.TraceWarning("Endpoint not writable. Refresh cache and retry"); + DefaultTrace.TraceWarning("ClientRetryPolicy: Endpoint not writable. Refresh cache and retry. Failed Location: {0}; ResourceAddress: {1}", + this.documentServiceRequest?.RequestContext?.LocationEndpointToRoute?.ToString() ?? string.Empty, + this.documentServiceRequest?.ResourceAddress ?? string.Empty); + return await this.ShouldRetryOnEndpointFailureAsync( isReadRequest: false, + markBothReadAndWriteAsUnavailable: false, forceRefresh: true, retryOnPreferredLocations: false); } @@ -174,9 +197,13 @@ private async Task ShouldRetryInternalAsync( && subStatusCode == SubStatusCodes.DatabaseAccountNotFound && (this.isReadRequest || this.canUseMultipleWriteLocations)) { - DefaultTrace.TraceWarning("Endpoint not available for reads. Refresh cache and retry"); + DefaultTrace.TraceWarning("ClientRetryPolicy: Endpoint not available for reads. Refresh cache and retry. Failed Location: {0}; ResourceAddress: {1}", + this.documentServiceRequest?.RequestContext?.LocationEndpointToRoute?.ToString() ?? string.Empty, + this.documentServiceRequest?.ResourceAddress ?? string.Empty); + return await this.ShouldRetryOnEndpointFailureAsync( isReadRequest: this.isReadRequest, + markBothReadAndWriteAsUnavailable: false, forceRefresh: false, retryOnPreferredLocations: false); } @@ -191,6 +218,12 @@ private async Task ShouldRetryInternalAsync( if (statusCode == HttpStatusCode.ServiceUnavailable && subStatusCode == SubStatusCodes.Unknown) { + DefaultTrace.TraceWarning("ClientRetryPolicy: ServiceUnavailable. Refresh cache and retry. Failed Location: {0}; ResourceAddress: {1}", + this.documentServiceRequest?.RequestContext?.LocationEndpointToRoute?.ToString() ?? string.Empty, + this.documentServiceRequest?.ResourceAddress ?? string.Empty); + + // Mark the partition as unavailable. + // Let the ClientRetry logic decide if the request should be retried this.partitionKeyRangeLocationCache.TryMarkEndpointUnavailableForPartitionKeyRange( this.documentServiceRequest); @@ -202,12 +235,15 @@ private async Task ShouldRetryInternalAsync( private async Task ShouldRetryOnEndpointFailureAsync( bool isReadRequest, + bool markBothReadAndWriteAsUnavailable, bool forceRefresh, bool retryOnPreferredLocations) { if (!this.enableEndpointDiscovery || this.failoverRetryCount > MaxRetryCount) { - DefaultTrace.TraceInformation("ShouldRetryOnEndpointFailureAsync() Not retrying. Retry count = {0}", this.failoverRetryCount); + DefaultTrace.TraceInformation("ClientRetryPolicy: ShouldRetryOnEndpointFailureAsync() Not retrying. Retry count = {0}, Endpoint = {1}", + this.failoverRetryCount, + this.locationEndpoint?.ToString() ?? string.Empty); return ShouldRetryResult.NoRetry(); } @@ -215,11 +251,12 @@ private async Task ShouldRetryOnEndpointFailureAsync( if (this.locationEndpoint != null) { - if (isReadRequest) + if (isReadRequest || markBothReadAndWriteAsUnavailable) { this.globalEndpointManager.MarkEndpointUnavailableForRead(this.locationEndpoint); } - else + + if (!isReadRequest || markBothReadAndWriteAsUnavailable) { this.globalEndpointManager.MarkEndpointUnavailableForWrite(this.locationEndpoint); } @@ -228,7 +265,7 @@ private async Task ShouldRetryOnEndpointFailureAsync( TimeSpan retryDelay = TimeSpan.Zero; if (!isReadRequest) { - DefaultTrace.TraceInformation("Failover happening. retryCount {0}", this.failoverRetryCount); + DefaultTrace.TraceInformation("ClientRetryPolicy: Failover happening. retryCount {0}", this.failoverRetryCount); if (this.failoverRetryCount > 1) { @@ -241,7 +278,7 @@ private async Task ShouldRetryOnEndpointFailureAsync( retryDelay = TimeSpan.FromMilliseconds(ClientRetryPolicy.RetryIntervalInMS); } - await this.globalEndpointManager.RefreshLocationAsync(null, forceRefresh); + await this.globalEndpointManager.RefreshLocationAsync(forceRefresh); int retryLocationIndex = this.failoverRetryCount; // Used to generate a round-robin effect if (retryOnPreferredLocations) @@ -320,7 +357,7 @@ private ShouldRetryResult ShouldRetryOnServiceUnavailable() { if (this.serviceUnavailableRetryCount++ >= ClientRetryPolicy.MaxServiceUnavailableRetryCount) { - DefaultTrace.TraceInformation($"ShouldRetryOnServiceUnavailable() Not retrying. Retry count = {this.serviceUnavailableRetryCount}."); + DefaultTrace.TraceInformation($"ClientRetryPolicy: ShouldRetryOnServiceUnavailable() Not retrying. Retry count = {this.serviceUnavailableRetryCount}."); return ShouldRetryResult.NoRetry(); } @@ -336,11 +373,11 @@ private ShouldRetryResult ShouldRetryOnServiceUnavailable() if (availablePreferredLocations <= 1) { // No other regions to retry on - DefaultTrace.TraceInformation($"ShouldRetryOnServiceUnavailable() Not retrying. No other regions available for the request. AvailablePreferredLocations = {availablePreferredLocations}."); + DefaultTrace.TraceInformation($"ClientRetryPolicy: ShouldRetryOnServiceUnavailable() Not retrying. No other regions available for the request. AvailablePreferredLocations = {availablePreferredLocations}."); return ShouldRetryResult.NoRetry(); } - DefaultTrace.TraceInformation($"ShouldRetryOnServiceUnavailable() Retrying. Received on endpoint {this.locationEndpoint}, IsReadRequest = {this.isReadRequest}."); + DefaultTrace.TraceInformation($"ClientRetryPolicy: ShouldRetryOnServiceUnavailable() Retrying. Received on endpoint {this.locationEndpoint}, IsReadRequest = {this.isReadRequest}."); // Retrying on second PreferredLocations // RetryCount is used as zero-based index diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 9a1202655b..bfb2afa720 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -100,6 +100,7 @@ public class CosmosClient : IDisposable private bool isDisposed = false; internal static int numberOfClientsCreated; + internal DateTime? DisposedDateTimeUtc { get; private set; } = null; static CosmosClient() { @@ -234,12 +235,7 @@ public CosmosClient( /// The cosmos service endpoint to use. /// The token to provide AAD token for authorization. /// (Optional) client options -#if PREVIEW - public -#else - internal -#endif - CosmosClient( + public CosmosClient( string accountEndpoint, TokenCredential tokenCredential, CosmosClientOptions clientOptions = null) @@ -260,7 +256,6 @@ public CosmosClient( this.AuthorizationTokenProvider = new AuthorizationTokenProviderTokenCredential( tokenCredential, this.Endpoint, - clientOptions.RequestTimeout, clientOptions.TokenCredentialBackgroundRefreshInterval); this.ClientContext = ClientContextCore.Create( @@ -385,12 +380,7 @@ public static async Task CreateAndInitializeAsync(string connectio /// /// A CosmosClient object. /// -#if PREVIEW - public -#else - internal -#endif - static async Task CreateAndInitializeAsync(string accountEndpoint, + public static async Task CreateAndInitializeAsync(string accountEndpoint, TokenCredential tokenCredential, IReadOnlyList<(string databaseId, string containerId)> containers, CosmosClientOptions cosmosClientOptions = null, @@ -506,7 +496,10 @@ internal CosmosClient( /// public virtual Task ReadAccountAsync() { - return ((IDocumentClientInternal)this.DocumentClient).GetDatabaseAccountInternalAsync(this.Endpoint); + return this.ClientContext.OperationHelperAsync( + nameof(ReadAccountAsync), + null, + (trace) => ((IDocumentClientInternal)this.DocumentClient).GetDatabaseAccountInternalAsync(this.Endpoint)); } /// @@ -1262,6 +1255,8 @@ protected virtual void Dispose(bool disposing) { if (!this.isDisposed) { + this.DisposedDateTimeUtc = DateTime.UtcNow; + if (disposing) { this.ClientContext.Dispose(); @@ -1270,13 +1265,5 @@ protected virtual void Dispose(bool disposing) this.isDisposed = true; } } - - private void ThrowIfDisposed() - { - if (this.isDisposed) - { - throw new ObjectDisposedException($"Accessing {nameof(CosmosClient)} after it is disposed is invalid."); - } - } } } diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index abc0c512d5..6cf95acaa5 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -161,14 +161,9 @@ public int GatewayModeMaxConnectionLimit /// This avoids latency issues because the old token is used until the new token is retrieved. /// /// - /// The recommended minimum value is 5 minutes. The default value is 25% of the token expire time. + /// The recommended minimum value is 5 minutes. The default value is 50% of the token expire time. /// -#if PREVIEW - public -#else - internal -#endif - TimeSpan? TokenCredentialBackgroundRefreshInterval { get; set; } + public TimeSpan? TokenCredentialBackgroundRefreshInterval { get; set; } /// /// Gets the handlers run before the process @@ -828,21 +823,42 @@ internal void SetUserAgentFeatures(UserAgentContainer userAgent) features |= CosmosClientOptionsFeatures.HttpClientFactory; } + string featureString = null; if (features != CosmosClientOptionsFeatures.NoFeatures) { - string featureString = Convert.ToString((int)features, 2).PadLeft(8, '0'); - if (!string.IsNullOrEmpty(featureString)) - { - userAgent.SetFeatures(featureString); - } + featureString = Convert.ToString((int)features, 2).PadLeft(8, '0'); } + string regionConfiguration = this.GetRegionConfiguration(); + userAgent.SetFeatures(featureString, regionConfiguration); + if (!string.IsNullOrEmpty(this.ApplicationName)) { userAgent.Suffix = this.ApplicationName; } } + /// + /// This generates a key that added to the user agent to make it + /// possible to determine if the SDK has region failover enabled. + /// + /// Format Reg-{D (Disabled discovery)}-S(application region)|L(List of preferred regions)|N(None, user did not configure it) + private string GetRegionConfiguration() + { + string regionConfig = this.LimitToEndpoint ? "D" : string.Empty; + if (!string.IsNullOrEmpty(this.ApplicationRegion)) + { + return regionConfig + "S"; + } + + if (this.ApplicationPreferredRegions != null) + { + return regionConfig + "L"; + } + + return regionConfig + "N"; + } + /// /// Serialize the current configuration into a JSON string /// diff --git a/Microsoft.Azure.Cosmos/src/DocumentClient.cs b/Microsoft.Azure.Cosmos/src/DocumentClient.cs index 1f3b1c248a..37630f4529 100644 --- a/Microsoft.Azure.Cosmos/src/DocumentClient.cs +++ b/Microsoft.Azure.Cosmos/src/DocumentClient.cs @@ -1353,10 +1353,7 @@ internal async Task ProcessRequestAsync( { await this.EnsureValidClientAsync(NoOpTrace.Singleton); - if (retryPolicyInstance != null) - { - retryPolicyInstance.OnBeforeSendRequest(request); - } + retryPolicyInstance?.OnBeforeSendRequest(request); using (new ActivityScope(Guid.NewGuid())) { @@ -1728,7 +1725,7 @@ public Task> CreateDocumentAsync(string documentsFeed private async Task> CreateDocumentInlineAsync(string documentsFeedOrDatabaseLink, object document, Documents.Client.RequestOptions options, bool disableAutomaticIdGeneration, CancellationToken cancellationToken) { IDocumentClientRetryPolicy requestRetryPolicy = this.ResetSessionTokenRetryPolicy.GetRequestPolicy(); - if (options == null || options.PartitionKey == null) + if (options?.PartitionKey == null) { requestRetryPolicy = new PartitionKeyMismatchRetryPolicy( await this.GetCollectionCacheAsync(NoOpTrace.Singleton), @@ -5480,17 +5477,14 @@ private async Task> ExecuteStoredProcedurePrivat headers)) { request.Headers[HttpConstants.HttpHeaders.XDate] = DateTime.UtcNow.ToString("r"); - if (options == null || options.PartitionKeyRangeId == null) + if (options?.PartitionKeyRangeId == null) { await this.AddPartitionKeyInformationAsync( request, options); } - if (retryPolicyInstance != null) - { - retryPolicyInstance.OnBeforeSendRequest(request); - } + retryPolicyInstance?.OnBeforeSendRequest(request); request.SerializerSettings = this.GetSerializerSettingsForRequest(options); return new StoredProcedureResponse(await this.ExecuteProcedureAsync( @@ -5683,7 +5677,7 @@ public Task> UpsertDocumentAsync(string documentsFeed private async Task> UpsertDocumentInlineAsync(string documentsFeedOrDatabaseLink, object document, Documents.Client.RequestOptions options, bool disableAutomaticIdGeneration, CancellationToken cancellationToken) { IDocumentClientRetryPolicy requestRetryPolicy = this.ResetSessionTokenRetryPolicy.GetRequestPolicy(); - if (options == null || options.PartitionKey == null) + if (options?.PartitionKey == null) { requestRetryPolicy = new PartitionKeyMismatchRetryPolicy( await this.GetCollectionCacheAsync(NoOpTrace.Singleton), @@ -6580,7 +6574,7 @@ private async Task InitializeGatewayConfigurationReaderAsync() AccountProperties accountProperties = this.accountServiceConfiguration.AccountProperties; this.UseMultipleWriteLocations = this.ConnectionPolicy.UseMultipleWriteLocations && accountProperties.EnableMultipleWriteLocations; - await this.GlobalEndpointManager.RefreshLocationAsync(accountProperties); + this.GlobalEndpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(accountProperties); } internal void CaptureSessionToken(DocumentServiceRequest request, DocumentServiceResponse response) @@ -6646,11 +6640,11 @@ private async Task AddPartitionKeyInformationAsync(DocumentServiceRequest reques PartitionKeyDefinition partitionKeyDefinition = collection.PartitionKey; PartitionKeyInternal partitionKey; - if (options != null && options.PartitionKey != null && options.PartitionKey.Equals(Documents.PartitionKey.None)) + if (options?.PartitionKey != null && options.PartitionKey.Equals(Documents.PartitionKey.None)) { partitionKey = collection.GetNoneValue(); } - else if (options != null && options.PartitionKey != null) + else if (options?.PartitionKey != null) { partitionKey = options.PartitionKey.InternalKey; } @@ -6671,7 +6665,7 @@ internal async Task AddPartitionKeyInformationAsync(DocumentServiceRequest reque // For backward compatibility, if collection doesn't have partition key defined, we assume all documents // have empty value for it and user doesn't need to specify it explicitly. PartitionKeyInternal partitionKey; - if (options == null || options.PartitionKey == null) + if (options?.PartitionKey == null) { if (partitionKeyDefinition == null || partitionKeyDefinition.Paths.Count == 0) { diff --git a/Microsoft.Azure.Cosmos/src/GatewayAccountReader.cs b/Microsoft.Azure.Cosmos/src/GatewayAccountReader.cs index 2bd73054ff..37d6add024 100644 --- a/Microsoft.Azure.Cosmos/src/GatewayAccountReader.cs +++ b/Microsoft.Azure.Cosmos/src/GatewayAccountReader.cs @@ -81,7 +81,10 @@ await this.cosmosAuthorization.AddAuthorizationHeaderAsync( public async Task InitializeReaderAsync() { AccountProperties databaseAccount = await GlobalEndpointManager.GetDatabaseAccountFromAnyLocationsAsync( - this.serviceEndpoint, this.connectionPolicy.PreferredLocations, this.GetDatabaseAccountAsync); + defaultEndpoint: this.serviceEndpoint, + locations: this.connectionPolicy.PreferredLocations, + getDatabaseAccountFn: this.GetDatabaseAccountAsync, + cancellationToken: default); return databaseAccount; } diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/HistogramEncoderV2.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/HistogramEncoderV2.cs new file mode 100644 index 0000000000..f16fc36eb4 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/HistogramEncoderV2.cs @@ -0,0 +1,96 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using HdrHistogram.Utilities; + +namespace HdrHistogram.Encoding +{ + /// + /// An implementation of for the V2 HdrHistogram log format. + /// + internal class HistogramEncoderV2 : IEncoder + { + /// + /// A singleton instance of the . + /// + public static readonly HistogramEncoderV2 Instance = new HistogramEncoderV2(); + + + /// + /// Encodes the supplied into the supplied . + /// + /// The data to encode. + /// The target to write to. + /// The number of bytes written. + public int Encode(IRecordedData data, ByteBuffer buffer) + { + int initialPosition = buffer.Position; + buffer.PutInt(data.Cookie); + int payloadLengthPosition = buffer.Position; + buffer.PutInt(0); // Placeholder for payload length in bytes. + buffer.PutInt(data.NormalizingIndexOffset); + buffer.PutInt(data.NumberOfSignificantValueDigits); + buffer.PutLong(data.LowestDiscernibleValue); + buffer.PutLong(data.HighestTrackableValue); + buffer.PutDouble(data.IntegerToDoubleValueConversionRatio); + + var payloadLength = FillBufferFromCountsArray(buffer, data); + buffer.PutInt(payloadLengthPosition, payloadLength); + + var bytesWritten = buffer.Position - initialPosition; + return bytesWritten; + } + + private static int FillBufferFromCountsArray(ByteBuffer buffer, IRecordedData data) + { + int startPosition = buffer.Position; + int srcIndex = 0; + + while (srcIndex < data.Counts.Length) + { + // V2 encoding format uses a ZigZag LEB128-64b9B encoded long. + // Positive values are counts, while negative values indicate a repeat zero counts. i.e. -4 indicates 4 sequential buckets with 0 counts. + long count = GetCountAtIndex(srcIndex++, data); + if (count < 0) + { + throw new InvalidOperationException($"Cannot encode histogram containing negative counts ({count}) at index {srcIndex}"); + } + // Count trailing 0s (which follow this count): + long zerosCount = 0; + if (count == 0) + { + zerosCount = 1; + while ((srcIndex < data.Counts.Length) && (GetCountAtIndex(srcIndex, data) == 0)) + { + zerosCount++; + srcIndex++; + } + } + if (zerosCount > 1) + { + ZigZagEncoding.PutLong(buffer, -zerosCount); + } + else + { + ZigZagEncoding.PutLong(buffer, count); + } + } + return buffer.Position - startPosition; + } + + private static long GetCountAtIndex(int idx, IRecordedData data) + { + return data.Counts[idx]; + //var normalizedIdx = NormalizeIndex(idx, data.NormalizingIndexOffset, data.Counts.Length); + //return data.Counts[idx]; + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IEncoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IEncoder.cs new file mode 100644 index 0000000000..27ae9e2b77 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IEncoder.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram.Encoding +{ + /// + /// Defines a method to allow histogram data to be encoded into a . + /// + internal interface IEncoder + { + /// + /// Encodes the supplied into the supplied . + /// + /// The data to encode. + /// The target to write to. + /// The number of bytes written. + int Encode(IRecordedData data, ByteBuffer buffer); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IHeader.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IHeader.cs new file mode 100644 index 0000000000..8fee91f050 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IHeader.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +namespace HdrHistogram.Encoding +{ + /// + /// Defines the header properties to be encoded for an HdrHistogram. + /// + internal interface IHeader + { + /// + /// The cookie value for the histogram. + /// + int Cookie { get; } + /// + /// The length in bytes of the payload body. + /// + int PayloadLengthInBytes { get; } + /// + /// The normalizing index offset. + /// + int NormalizingIndexOffset { get; } //Not currently implemented/used. + /// + /// THe number of significant digits that values are measured to. + /// + int NumberOfSignificantValueDigits { get; } + /// + /// The lowest trackable value for the histogram + /// + long LowestTrackableUnitValue { get; } + /// + /// The highest trackable value for the histogram + /// + long HighestTrackableValue { get; } + /// + /// Integer to double conversion ratio. + /// + double IntegerToDoubleValueConversionRatio { get; } //Not currently implemented/used. + /// + /// The amount of excess capacity that will not be needed. + /// + int CapacityEstimateExcess { get; } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IRecordedData.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IRecordedData.cs new file mode 100644 index 0000000000..cb5bdd945e --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/IRecordedData.cs @@ -0,0 +1,47 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +namespace HdrHistogram.Encoding +{ + /// + /// Defines the histogram data to be recorded + /// + internal interface IRecordedData + { + /// + /// The cookie value for the histogram. + /// + int Cookie { get; } + /// + /// The normalizing index offset. + /// + int NormalizingIndexOffset { get; } //Required? What is it? + /// + /// THe number of significant digits that values are measured to. + /// + int NumberOfSignificantValueDigits { get; } + /// + /// The lowest trackable value for the histogram + /// + long LowestDiscernibleValue { get; } //TODO: Use either LowestDiscernibleValue or LowestTrackableUnitValue but not both. -LC + /// + /// The highest trackable value for the histogram + /// + long HighestTrackableValue { get; } + /// + /// Integer to double conversion ratio. + /// + double IntegerToDoubleValueConversionRatio { get; } + /// + /// The actual array of counts. + /// + long[] Counts { get; } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/RecordedData.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/RecordedData.cs new file mode 100644 index 0000000000..70a13cfb93 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/RecordedData.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +namespace HdrHistogram.Encoding +{ + internal sealed class RecordedData : IRecordedData + { + public RecordedData(int cookie, int normalizingIndexOffset, int numberOfSignificantValueDigits, long lowestDiscernibleValue, long highestTrackableValue, double integerToDoubleValueConversionRatio, long[] counts) + { + Cookie = cookie; + NormalizingIndexOffset = normalizingIndexOffset; + NumberOfSignificantValueDigits = numberOfSignificantValueDigits; + LowestDiscernibleValue = lowestDiscernibleValue; + HighestTrackableValue = highestTrackableValue; + IntegerToDoubleValueConversionRatio = integerToDoubleValueConversionRatio; + Counts = counts; + } + + public int Cookie { get; } + public int NormalizingIndexOffset { get; } + public int NumberOfSignificantValueDigits { get; } + public long LowestDiscernibleValue { get; } + public long HighestTrackableValue { get; } + public double IntegerToDoubleValueConversionRatio { get; } + public long[] Counts { get; } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/V0Header.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/V0Header.cs new file mode 100644 index 0000000000..84c68ee711 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/V0Header.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram.Encoding +{ + internal sealed class V0Header : IHeader + { + public V0Header(int cookie, ByteBuffer buffer) + { + Cookie = cookie; + NumberOfSignificantValueDigits = buffer.GetInt(); + LowestTrackableUnitValue = buffer.GetLong(); + HighestTrackableValue = buffer.GetLong(); + PayloadLengthInBytes = int.MaxValue; + IntegerToDoubleValueConversionRatio = 1.0; + NormalizingIndexOffset = 0; + } + public int Cookie { get; } + public int PayloadLengthInBytes { get; } + public int NormalizingIndexOffset { get; } + public int NumberOfSignificantValueDigits { get; } + public long LowestTrackableUnitValue { get; } + public long HighestTrackableValue { get; } + public double IntegerToDoubleValueConversionRatio { get; } + public int CapacityEstimateExcess => 32; + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/V1Header.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/V1Header.cs new file mode 100644 index 0000000000..f8b1884ad2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Encoding/V1Header.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram.Encoding +{ + internal sealed class V1Header : IHeader + { + public V1Header(int cookie, ByteBuffer buffer) + { + Cookie = cookie; + PayloadLengthInBytes = buffer.GetInt(); + NormalizingIndexOffset = buffer.GetInt(); + NumberOfSignificantValueDigits = buffer.GetInt(); + LowestTrackableUnitValue = buffer.GetLong(); + HighestTrackableValue = buffer.GetLong(); + IntegerToDoubleValueConversionRatio = buffer.GetDouble(); + } + + public int Cookie { get; } + public int PayloadLengthInBytes { get; } + public int NormalizingIndexOffset { get; } + public int NumberOfSignificantValueDigits { get; } + public long LowestTrackableUnitValue { get; } + public long HighestTrackableValue { get; } + public double IntegerToDoubleValueConversionRatio { get; } + public int CapacityEstimateExcess => 0; + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Histogram.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Histogram.cs new file mode 100644 index 0000000000..888bea0a61 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Histogram.cs @@ -0,0 +1,342 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; + +namespace HdrHistogram +{ + /// + /// Provides factory methods to define the features of your histogram. + /// + internal abstract class HistogramFactory + { + /// + /// Private constructor to force usage via the Static starter methods. + /// + private HistogramFactory() + { + } + + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + protected long LowestTrackableValue { get; set; } = 1; + + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// + protected long HighestTrackableValue { get; set; } = TimeStamp.Minutes(10); + + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + protected int NumberOfSignificantValueDigits { get; set; } = 3; + + + + /// + /// Specifies that the Histogram to be created should be thread safe when written to from multiple threads. + /// + /// Returns a that is set to return a threadsafe writer. + public abstract HistogramFactory WithThreadSafeWrites(); + + /// + /// Specifies that the consumer will need to be able to read Histogram values in a thread safe manner. + /// This will mean will be used to wrap the Histogram, allowing thread safe reads. + /// + /// Returns a which can create recorders. Recorders allow for threadsafe reads. + public abstract RecorderFactory WithThreadSafeReads(); + + /// + /// A factory-method to create the Histogram. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// Returns a newly created instance defined by the settings of the current instance of . + public abstract HistogramBase Create( + long lowestDiscernibleValue, + long highestTrackableValue, + int numberOfSignificantValueDigits); + + /// + /// A factory-method to create the Histogram. + /// + /// An identifier for this instance. + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// Returns a newly created instance defined by the settings of the current instance of . + public abstract HistogramBase Create( + long instanceId, + long lowestDiscernibleValue, + long highestTrackableValue, + int numberOfSignificantValueDigits); + + /// + /// Specifies the lowest value the Histogram should be configured to record. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The configured with the specified minimum allowed value. + public HistogramFactory WithValuesFrom(long lowestDiscernibleValue) + { + LowestTrackableValue = lowestDiscernibleValue; + return this; + } + + /// + /// Specifies the highest value the Histogram should be configured to record. + /// + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// + /// The configured with the specified maximum allowed value. + public HistogramFactory WithValuesUpTo(long highestTrackableValue) + { + HighestTrackableValue = highestTrackableValue; + return this; + } + + /// + /// Specifies the number of significant figures that the Histogram should record. + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// The configured with the specified maximum allowed value. + public HistogramFactory WithPrecisionOf(int numberOfSignificantValueDigits) + { + NumberOfSignificantValueDigits = numberOfSignificantValueDigits; + return this; + } + + /// + /// Creates the histogram as configured by this factory instance. + /// + /// A newly created instance of . + public HistogramBase Create() + { + return Create(LowestTrackableValue, HighestTrackableValue, NumberOfSignificantValueDigits); + } + + + + + /// + /// Specify that the Histogram should be able to record count values in the 64bit range. + /// + /// The configured for 64bit bucket sizes. + public static HistogramFactory With64BitBucketSize() + { + return new LongHistogramFactory(); + } + + /// + /// Specify that the Histogram should be able to record count values in the 32bit range. + /// + /// The configured for 64bit bucket sizes. + public static HistogramFactory With32BitBucketSize() + { + return new IntHistogramFactory(); + } + + /// + /// Specify that the Histogram should be able to record count values in the 32bit range. + /// + /// The configured for 64bit bucket sizes. + public static HistogramFactory With16BitBucketSize() + { + return new ShortHistogramFactory(); + } + + + + private sealed class LongHistogramFactory : HistogramFactory + { + public override HistogramBase Create(long lowestDiscernibleValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new LongHistogram(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramBase Create(long instanceId, long lowestDiscernibleValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + { + return new LongHistogram(instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramFactory WithThreadSafeWrites() + { + return new LongConcurrentHistogramFactory(this); + } + + public override RecorderFactory WithThreadSafeReads() + { + return new RecorderFactory(this); + } + } + + private sealed class IntHistogramFactory : HistogramFactory + { + public override HistogramBase Create(long lowestDiscernibleValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new IntHistogram(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramBase Create(long instanceId, long lowestDiscernibleValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + { + return new IntHistogram(instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramFactory WithThreadSafeWrites() + { + return new IntConcurrentHistogramFactory(this); + } + + public override RecorderFactory WithThreadSafeReads() + { + return new RecorderFactory(this); + } + } + + private sealed class ShortHistogramFactory : HistogramFactory + { + public override HistogramBase Create(long lowestDiscernibleValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new ShortHistogram(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramBase Create(long instanceId, long lowestDiscernibleValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + { + return new ShortHistogram(instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramFactory WithThreadSafeWrites() + { + throw new NotSupportedException("Short(16bit) Histograms do not support thread safe writes."); + } + + public override RecorderFactory WithThreadSafeReads() + { + return new RecorderFactory(this); + } + } + + private sealed class LongConcurrentHistogramFactory : HistogramFactory + { + public LongConcurrentHistogramFactory(HistogramFactory histogramFactory) + { + LowestTrackableValue = histogramFactory.LowestTrackableValue; + HighestTrackableValue = histogramFactory.HighestTrackableValue; + NumberOfSignificantValueDigits = histogramFactory.NumberOfSignificantValueDigits; + } + + public override HistogramFactory WithThreadSafeWrites() + { + return this; + } + + public override RecorderFactory WithThreadSafeReads() + { + return new RecorderFactory(this); + } + + public override HistogramBase Create(long lowestDiscernibleValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new LongConcurrentHistogram(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramBase Create(long instanceId, long lowestDiscernibleValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + { + return new LongConcurrentHistogram(instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + } + + private sealed class IntConcurrentHistogramFactory : HistogramFactory + { + public IntConcurrentHistogramFactory(HistogramFactory histogramFactory) + { + LowestTrackableValue = histogramFactory.LowestTrackableValue; + HighestTrackableValue = histogramFactory.HighestTrackableValue; + NumberOfSignificantValueDigits = histogramFactory.NumberOfSignificantValueDigits; + } + + public override HistogramFactory WithThreadSafeWrites() + { + return this; + } + + public override RecorderFactory WithThreadSafeReads() + { + return new RecorderFactory(this); + } + + public override HistogramBase Create(long lowestDiscernibleValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new IntConcurrentHistogram(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + + public override HistogramBase Create(long instanceId, long lowestDiscernibleValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + { + return new IntConcurrentHistogram(instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + } + } + + + /// + /// Factory for creating Recorders for thread safe reading of histograms. + /// + public sealed class RecorderFactory + { + private readonly HistogramFactory _histogramBuilder; + + internal RecorderFactory(HistogramFactory histogramBuilder) + { + _histogramBuilder = histogramBuilder; + } + + /// + /// Creates the recorder as configured by this factory instance. + /// + /// A newly created instance of . + public Recorder Create() + { + return new Recorder( + _histogramBuilder.LowestTrackableValue, + _histogramBuilder.HighestTrackableValue, + _histogramBuilder.NumberOfSignificantValueDigits, + _histogramBuilder.Create); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramBase.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramBase.cs new file mode 100644 index 0000000000..b8888da199 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramBase.cs @@ -0,0 +1,743 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading; +using HdrHistogram.Encoding; +using HdrHistogram.Iteration; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// Base class for High Dynamic Range (HDR) Histograms + /// + /// + /// supports the recording and analyzing sampled data value counts across a configurable + /// integer value range with configurable value precision within the range. + /// Value precision is expressed as the number of significant digits in the value recording, and provides control over + /// value quantization behavior across the value range and the subsequent value resolution at any given level. + /// + /// For example, a Histogram could be configured to track the counts of observed integer values between 0 and + /// 36,000,000,000 while maintaining a value precision of 3 significant digits across that range. + /// Value quantization within the range will thus be no larger than 1/1,000th (or 0.1%) of any value. + /// This example Histogram could be used to track and analyze the counts of observed response times ranging between + /// 100 nanoseconds and 1 hour in magnitude, while maintaining a value resolution of 100 nanosecond up to + /// 100 microseconds, a resolution of 1 millisecond(or better) up to one second, and a resolution of 1 second + /// (or better) up to 1,000 seconds. + /// At it's maximum tracked value(1 hour), it would still maintain a resolution of 3.6 seconds (or better). + /// + /// + internal abstract class HistogramBase : IRecorder + { + private static readonly Regex TagValidation = new Regex("[, \\r\\n]", RegexOptions.Compiled); + private static long _instanceIdSequencer = -1; + + private readonly int _subBucketHalfCountMagnitude; + private readonly int _unitMagnitude; + private readonly long _subBucketMask; + private readonly int _bucketIndexOffset; + private long _maxValue; + private long _minNonZeroValue; + private string _tag; + + /// + /// An identifier for the Histogram. Maybe generated by the Recorder if used. + /// + public long InstanceId { get; } + + /// + /// Get the configured highestTrackableValue + /// + /// highestTrackableValue + public long HighestTrackableValue { get; } + + /// + /// Get the configured lowestTrackableValue + /// + /// lowestTrackableValue + public long LowestTrackableValue { get; } + + /// + /// Get the configured numberOfSignificantValueDigits + /// + /// numberOfSignificantValueDigits + public int NumberOfSignificantValueDigits { get; } + + /// + /// Gets or Sets the start time stamp value associated with this histogram to a given value. + /// By convention in milliseconds since the epoch. + /// + public long StartTimeStamp { get; set; } + + /// + /// Gets or Sets the end time stamp value associated with this histogram to a given value. + /// By convention in milliseconds since the epoch. + /// + public long EndTimeStamp { get; set; } + + /// + /// Gets or Sets the optional Tag string associated with this histogram. + /// + public string Tag + { + get { return _tag; } + set + { + if(!string.IsNullOrEmpty(value) && TagValidation.IsMatch(value)) + throw new ArgumentException("Tag string cannot contain commas, spaces, or line breaks."); + _tag = value; + } + } + + + /// + /// Gets the total number of recorded values. + /// + public abstract long TotalCount { get; protected set; } + + + /// + /// The number of buckets used to store count values. + /// + public int BucketCount { get; } + /// + /// The number of sub-buckets used to store count values. + /// + public int SubBucketCount { get; } + + internal int SubBucketHalfCount { get; } + + /// + /// The length of the internal array that stores the counts. + /// + internal int CountsArrayLength { get; } + + /// + /// Returns the word size of this implementation + /// + protected abstract int WordSizeInBytes { get; } + + /// + /// The maximum value a count can be for any given bucket. + /// + protected abstract long MaxAllowableCount { get; } + + + /// + /// Construct a histogram given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in nanoseconds, where the minimal accuracy required is a + /// microsecond, the proper value for would be 1000. + /// + protected HistogramBase(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + :this(Interlocked.Decrement(ref _instanceIdSequencer), lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + } + + /// + /// Construct a histogram given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// An identifier for this instance. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in ticks (100 nanoseconds), where the minimal accuracy required is a + /// microsecond, the proper value for lowestTrackableValue would be 10. + /// + protected HistogramBase(long instanceId, long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + if (lowestTrackableValue < 1) throw new ArgumentException("lowestTrackableValue must be >= 1", nameof(lowestTrackableValue)); + if (highestTrackableValue < 2 * lowestTrackableValue) throw new ArgumentException("highestTrackableValue must be >= 2 * lowestTrackableValue", nameof(highestTrackableValue)); + if ((numberOfSignificantValueDigits < 0) || (numberOfSignificantValueDigits > 5)) throw new ArgumentException("numberOfSignificantValueDigits must be between 0 and 5", nameof(numberOfSignificantValueDigits)); + + InstanceId = instanceId; + LowestTrackableValue = lowestTrackableValue; + HighestTrackableValue = highestTrackableValue; + NumberOfSignificantValueDigits = numberOfSignificantValueDigits; + + _unitMagnitude = (int)Math.Floor(Math.Log(LowestTrackableValue) / Math.Log(2)); + + // We need to maintain power-of-two subBucketCount (for clean direct indexing) that is large enough to + // provide unit resolution to at least largestValueWithSingleUnitResolution. So figure out + // largestValueWithSingleUnitResolution's nearest power-of-two (rounded up), and use that: + var largestValueWithSingleUnitResolution = 2 * (long)Math.Pow(10, NumberOfSignificantValueDigits); + var subBucketCountMagnitude = (int)Math.Ceiling(Math.Log(largestValueWithSingleUnitResolution) / Math.Log(2)); + + _subBucketHalfCountMagnitude = ((subBucketCountMagnitude > 1) ? subBucketCountMagnitude : 1) - 1; + SubBucketCount = (int)Math.Pow(2, (_subBucketHalfCountMagnitude + 1)); + SubBucketHalfCount = SubBucketCount / 2; + _subBucketMask = (SubBucketCount - 1) << _unitMagnitude; + + + _bucketIndexOffset = 64 - _unitMagnitude - (_subBucketHalfCountMagnitude + 1); + + + // determine exponent range needed to support the trackable value with no overflow: + BucketCount = GetBucketsNeededToCoverValue(HighestTrackableValue); + + CountsArrayLength = GetLengthForNumberOfBuckets(BucketCount); + } + + /// + /// Copies the data from this instance to a new instance. + /// + /// A new copy of this instance. + public abstract HistogramBase Copy(); + + /// + /// Records a value in the histogram + /// + /// The value to be recorded + /// if value is exceeds highestTrackableValue + public void RecordValue(long value) + { + RecordSingleValue(value); + } + + /// + /// Record a value in the histogram (adding to the value's current count) + /// + /// The value to be recorded + /// The number of occurrences of this value to record + /// if value is exceeds highestTrackableValue + public void RecordValueWithCount(long value, long count) + { + // Dissect the value into bucket and sub-bucket parts, and derive index into counts array: + var bucketIndex = GetBucketIndex(value); + var subBucketIndex = GetSubBucketIndex(value, bucketIndex); + var countsIndex = CountsArrayIndex(bucketIndex, subBucketIndex); + AddToCountAtIndex(countsIndex, count); + } + + /// + /// Record a value in the histogram. + /// + /// The value to record + /// If is larger than 0, add auto-generated value records as appropriate if is larger than + /// if value is exceeds highestTrackableValue + /// + /// To compensate for the loss of sampled values when a recorded value is larger than the expected interval between value samples, + /// Histogram will auto-generate an additional series of decreasingly-smaller (down to the expectedIntervalBetweenValueSamples) value records. + /// + /// Note: This is a at-recording correction method, as opposed to the post-recording correction method provided by currently unimplemented CopyCorrectedForCoordinatedOmission method. + /// The two methods are mutually exclusive, and only one of the two should be be used on a given data set to correct for the same coordinated omission issue. + /// + /// See notes in the description of the Histogram calls for an illustration of why this corrective behavior is important. + /// + public void RecordValueWithExpectedInterval(long value, long expectedIntervalBetweenValueSamples) + { + RecordValueWithCountAndExpectedInterval(value, 1, expectedIntervalBetweenValueSamples); + } + + /// + /// Reset the contents and stats of this histogram + /// + public void Reset() + { + ClearCounts(); + } + + /// + /// Add the contents of another histogram to this one. + /// + /// The other histogram. + /// if values in fromHistogram's are higher than highestTrackableValue. + public virtual void Add(HistogramBase fromHistogram) + { + if (HighestTrackableValue < fromHistogram.HighestTrackableValue) + { + throw new ArgumentOutOfRangeException(nameof(fromHistogram), $"The other histogram covers a wider range ({fromHistogram.HighestTrackableValue} than this one ({HighestTrackableValue})."); + } + if ((BucketCount == fromHistogram.BucketCount) && + (SubBucketCount == fromHistogram.SubBucketCount) && + (_unitMagnitude == fromHistogram._unitMagnitude)) + { + // Counts arrays are of the same length and meaning, so we can just iterate and add directly: + for (var i = 0; i < fromHistogram.CountsArrayLength; i++) + { + AddToCountAtIndex(i, fromHistogram.GetCountAtIndex(i)); + } + } + else + { + // Arrays are not a direct match, so we can't just stream through and add them. + // Instead, go through the array and add each non-zero value found at it's proper value: + for (var i = 0; i < fromHistogram.CountsArrayLength; i++) + { + var count = fromHistogram.GetCountAtIndex(i); + RecordValueWithCount(fromHistogram.ValueFromIndex(i), count); + } + } + } + + /// + /// Get the size (in value units) of the range of values that are equivalent to the given value within the histogram's resolution. + /// Where "equivalent" means that value samples recorded for any two equivalent values are counted in a common total count. + /// + /// The given value + /// The lowest value that is equivalent to the given value within the histogram's resolution. + public long SizeOfEquivalentValueRange(long value) + { + var bucketIndex = GetBucketIndex(value); + var subBucketIndex = GetSubBucketIndex(value, bucketIndex); + if (subBucketIndex >= SubBucketCount) + { + //TODO: Validate the conditions under which this is hit. Missing unit test (just copy from Java) -LC + bucketIndex++; + } + + var distanceToNextValue = 1 << (_unitMagnitude + bucketIndex); + return distanceToNextValue; + } + + /// + /// Get the lowest value that is equivalent to the given value within the histogram's resolution. + /// Where "equivalent" means that value samples recorded for any two equivalent values are counted in a common total count. + /// + /// The given value + /// The lowest value that is equivalent to the given value within the histogram's resolution. + public long LowestEquivalentValue(long value) + { + var bucketIndex = GetBucketIndex(value); + var subBucketIndex = GetSubBucketIndex(value, bucketIndex); + return ValueFromIndex(bucketIndex, subBucketIndex); + } + + /// + /// Get a value that lies in the middle (rounded up) of the range of values equivalent the given value. + /// Where "equivalent" means that value samples recorded for any two equivalent values are counted in a common total count. + /// + /// The given value + /// The value lies in the middle (rounded up) of the range of values equivalent the given value. + public long MedianEquivalentValue(long value) + { + return LowestEquivalentValue(value) + + (SizeOfEquivalentValueRange(value) >> 1); + } + + /// + /// Get the next value that is not equivalent to the given value within the histogram's resolution. + /// Where "equivalent" means that value samples recorded for any two equivalent values are counted in a common total count. + /// + /// The given value + /// The next value that is not equivalent to the given value within the histogram's resolution. + public long NextNonEquivalentValue(long value) + { + return LowestEquivalentValue(value) + SizeOfEquivalentValueRange(value); + } + + /// + /// Get the value at a given percentile + /// + /// The percentile to get the value for + /// The value a given percentage of all recorded value entries in the histogram fall below. + public long GetValueAtPercentile(double percentile) + { + var requestedPercentile = Math.Min(percentile, 100.0); // Truncate down to 100% + var countAtPercentile = (long)(((requestedPercentile / 100.0) * TotalCount) + 0.5); // round to nearest + countAtPercentile = Math.Max(countAtPercentile, 1); // Make sure we at least reach the first recorded entry + long runningCount = 0; + for (var i = 0; i < BucketCount; i++) + { + var j = (i == 0) ? 0 : (SubBucketCount / 2); + for (; j < SubBucketCount; j++) + { + runningCount += GetCountAt(i, j); + if (runningCount >= countAtPercentile) + { + var valueAtIndex = ValueFromIndex(i, j); + return this.HighestEquivalentValue(valueAtIndex); + } + } + } + throw new ArgumentOutOfRangeException(nameof(percentile), "percentile value not found in range"); // should not reach here. + } + + /// + /// Get the count of recorded values at a specific value + /// + /// The value for which to provide the recorded count + /// The total count of values recorded in the histogram at the given value (to within the histogram resolution at the value level). + /// On parameters that are outside the tracked value range + public long GetCountAtValue(long value) + { + var bucketIndex = GetBucketIndex(value); + var subBucketIndex = GetSubBucketIndex(value, bucketIndex); + // May throw ArrayIndexOutOfBoundsException: + return GetCountAt(bucketIndex, subBucketIndex); + } + + + /// + /// Provide a means of iterating through all recorded histogram values using the finest granularity steps supported by the underlying representation. + /// The iteration steps through all non-zero recorded value counts, and terminates when all recorded histogram values are exhausted. + /// + /// An enumerator of through the histogram using a + public IEnumerable RecordedValues() + { + return new RecordedValuesEnumerable(this); + } + + /// + /// Provide a means of iterating through all histogram values using the finest granularity steps supported by the underlying representation. + /// The iteration steps through all possible unit value levels, regardless of whether or not there were recorded values for that value level, and terminates when all recorded histogram values are exhausted. + /// + /// An enumerator of through the histogram using a + public IEnumerable AllValues() + { + return new AllValueEnumerable(this); + } + + /// + /// Get the capacity needed to encode this histogram into a + /// + /// the capacity needed to encode this histogram into a + public int GetNeededByteBufferCapacity() + { + return GetNeededByteBufferCapacity(CountsArrayLength); + } + + /// + /// Encode this histogram into a + /// + /// The buffer to encode into + /// The encoder to use + /// The number of bytes written to the buffer + public int Encode(ByteBuffer targetBuffer, IEncoder encoder) + { + var data = GetData(); + return encoder.Encode(data, targetBuffer); + } + + /// + /// Determine if this histogram had any of it's value counts overflow. + /// + /// true if this histogram has had a count value overflow, else false. + /// + /// Since counts are kept in fixed integer form with potentially limited range (e.g. int and short), a specific value range count could potentially overflow, leading to an inaccurate and misleading histogram representation. + /// This method accurately determines whether or not an overflow condition has happened in an or . + /// + public bool HasOverflowed() + { + // On overflow, the totalCount accumulated counter will (always) not match the total of counts + var totalCounted = 0L; + for (var i = 0; i < CountsArrayLength; i++) + { + totalCounted += GetCountAtIndex(i); + //If adding has wrapped back around (for Long implementations) + if (totalCounted < 0) + return true; + } + return (totalCounted != TotalCount); + } + + /// + /// Provide a (conservatively high) estimate of the Histogram's total footprint in bytes + /// + /// a (conservatively high) estimate of the Histogram's total footprint in bytes + public virtual int GetEstimatedFootprintInBytes() + { + return (512 + (WordSizeInBytes * CountsArrayLength)); + } + + internal long GetCountAt(int bucketIndex, int subBucketIndex) + { + return GetCountAtIndex(CountsArrayIndex(bucketIndex, subBucketIndex)); + } + + internal long ValueFromIndex(int bucketIndex, int subBucketIndex) + { + return ((long)subBucketIndex) << (bucketIndex + _unitMagnitude); + } + + + /// + /// Copies data from the provided buffer into the internal counts array. + /// + /// The buffer to read from. + /// The length of the buffer to read. + /// The word size in bytes. + internal int FillCountsFromBuffer(ByteBuffer buffer, int length, int wordSizeInBytes) + { + var countsDecoder = Persistence.CountsDecoder.GetDecoderForWordSize(wordSizeInBytes); + + return countsDecoder.ReadCounts(buffer, + length, + CountsArrayLength, + (idx, count) => + { + if (count > MaxAllowableCount) + { + throw new ArgumentException($"An encoded count ({count}) does not fit in the Histogram's ({WordSizeInBytes} bytes) was encountered in the source"); + } + SetCountAtIndex(idx, count); + }); + } + + internal void EstablishInternalTackingValues(int lengthToCover) + { + ResetMaxValue(0); + ResetMinNonZeroValue(long.MaxValue); + int maxIndex = -1; + int minNonZeroIndex = -1; + long observedTotalCount = 0; + for (int index = 0; index < lengthToCover; index++) + { + long countAtIndex; + if ((countAtIndex = GetCountAtIndex(index)) > 0) + { + observedTotalCount += countAtIndex; + maxIndex = index; + if ((minNonZeroIndex == -1) && (index != 0)) + { + minNonZeroIndex = index; + } + } + } + if (maxIndex >= 0) + { + UpdatedMaxValue(this.HighestEquivalentValue(ValueFromIndex(maxIndex))); + } + if (minNonZeroIndex >= 0) + { + UpdateMinNonZeroValue(ValueFromIndex(minNonZeroIndex)); + } + TotalCount = observedTotalCount; + } + + /// + /// Gets the number of recorded values at a given index. + /// + /// The index to get the count for + /// The number of recorded values at the given index. + protected abstract long GetCountAtIndex(int index); + + /// + /// Sets the count at the given index. + /// + /// The index to be set + /// The value to set + protected abstract void SetCountAtIndex(int index, long value); + + /// + /// Increments the count at the given index. Will also increment the . + /// + /// The index to increment the count at. + protected abstract void IncrementCountAtIndex(int index); + + /// + /// Adds the specified amount to the count of the provided index. Also increments the by the same amount. + /// + /// The index to increment. + /// The amount to increment by. + protected abstract void AddToCountAtIndex(int index, long addend); + + /// + /// Clears the counts of this implementation. + /// + protected abstract void ClearCounts(); + + /// + /// Copies the internal counts array into the supplied array. + /// + /// The array to write each count value into. + protected abstract void CopyCountsInto(long[] target); + + /// + /// Set internally tracked _maxValue to new value if new value is greater than current one. + /// May be overridden by subclasses for synchronization or atomicity purposes. + /// + /// new _maxValue to set + private void UpdatedMaxValue(long value) + { + while (value > _maxValue) + { + ////TODO: Perform atomic CAS operation here -LC + //maxValueUpdater.compareAndSet(this, _maxValue, value); + _maxValue = value; + } + } + + /// + /// Set internally tracked _minNonZeroValue to new value if new value is smaller than current one. + /// May be overridden by subclasses for synchronization or atomicity purposes. + /// + /// new _minNonZeroValue to set + private void UpdateMinNonZeroValue(long value) + { + while (value < _minNonZeroValue) + { + //TODO: Perform atomic CAS operation here -LC + //minNonZeroValueUpdater.compareAndSet(this, _minNonZeroValue, value); + _minNonZeroValue = value; + } + } + + + private void ResetMinNonZeroValue(long minNonZeroValue) + { + _minNonZeroValue = minNonZeroValue; + } + + private void ResetMaxValue(long maxValue) + { + _maxValue = maxValue; + } + + private void RecordSingleValue(long value) + { + // Dissect the value into bucket and sub-bucket parts, and derive index into counts array: + var bucketIndex = GetBucketIndex(value); + var subBucketIndex = GetSubBucketIndex(value, bucketIndex); + var countsIndex = CountsArrayIndex(bucketIndex, subBucketIndex); + IncrementCountAtIndex(countsIndex); + } + + private void RecordValueWithCountAndExpectedInterval(long value, long count, long expectedIntervalBetweenValueSamples) + { + RecordValueWithCount(value, count); + if (expectedIntervalBetweenValueSamples <= 0) + return; + for (var missingValue = value - expectedIntervalBetweenValueSamples; + missingValue >= expectedIntervalBetweenValueSamples; + missingValue -= expectedIntervalBetweenValueSamples) + { + RecordValueWithCount(missingValue, count); + } + } + + private int GetNeededByteBufferCapacity(int relevantLength) + { + return (relevantLength * WordSizeInBytes) + 32; + } + + private int GetBucketsNeededToCoverValue(long value) + { + long trackableValue = (SubBucketCount - 1) << _unitMagnitude; + var bucketsNeeded = 1; + while (trackableValue < value && trackableValue > 0) + { + trackableValue <<= 1; + bucketsNeeded++; + } + return bucketsNeeded; + } + + private int GetLengthForNumberOfBuckets(int numberOfBuckets) + { + var lengthNeeded = (numberOfBuckets + 1) * (SubBucketCount / 2); + return lengthNeeded; + } + + private int CountsArrayIndex(int bucketIndex, int subBucketIndex) + { + Debug.Assert(subBucketIndex < SubBucketCount); + Debug.Assert(bucketIndex == 0 || (subBucketIndex >= SubBucketHalfCount)); + // Calculate the index for the first entry in the bucket: + // (The following is the equivalent of ((bucketIndex + 1) * subBucketHalfCount) ): + var bucketBaseIndex = (bucketIndex + 1) << _subBucketHalfCountMagnitude; + // Calculate the offset in the bucket: + var offsetInBucket = subBucketIndex - SubBucketHalfCount; + // The following is the equivalent of ((subBucketIndex - subBucketHalfCount) + bucketBaseIndex; + return bucketBaseIndex + offsetInBucket; + } + + //Optimization. This simple method should be in-lined by the JIT compiler, allowing hot path `GetBucketIndex(long, long, int)` to become static. -LC + private int GetBucketIndex(long value) + { + return GetBucketIndex(value, _subBucketMask, _bucketIndexOffset); + } + private static int GetBucketIndex(long value, long subBucketMask, int bucketIndexOffset) + { + var leadingZeros = Bitwise.NumberOfLeadingZeros(value | subBucketMask); // smallest power of 2 containing value + return bucketIndexOffset - leadingZeros; + } + + private int GetSubBucketIndex(long value, int bucketIndex) + { + return (int)(value >> (bucketIndex + _unitMagnitude)); + } + + private long ValueFromIndex(int index) + { + var bucketIndex = (index >> _subBucketHalfCountMagnitude) - 1; + var subBucketIndex = (index & (SubBucketHalfCount - 1)) + SubBucketHalfCount; + if (bucketIndex < 0) + { + subBucketIndex -= SubBucketHalfCount; + bucketIndex = 0; + } + return ValueFromIndex(bucketIndex, subBucketIndex); + } + + private IRecordedData GetData() + { + var relevantCounts = GetRelevantCounts(); + return new RecordedData( + this.GetEncodingCookie(), + 0, + this.NumberOfSignificantValueDigits, + this.LowestTrackableValue, + this.HighestTrackableValue, + 1.0, + relevantCounts + ); + } + + private long[] GetRelevantCounts() + { + var max = this.GetMaxValue(); + var bucketIdx = this.GetBucketIndex(max); + var subBucketIdx = this.GetSubBucketIndex(max, bucketIdx); + var maxIdx = this.CountsArrayIndex(bucketIdx, subBucketIdx); + var length = maxIdx + 1; + var relevantCounts = new long[length]; + CopyCountsInto(relevantCounts); + return relevantCounts; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramEncoding.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramEncoding.cs new file mode 100644 index 0000000000..6b48c94367 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramEncoding.cs @@ -0,0 +1,257 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using HdrHistogram.Encoding; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// Exposes functionality to encode and decode types. + /// + internal static class HistogramEncoding + { + private const int UncompressedDoubleHistogramEncodingCookie = 0x0c72124e; + private const int CompressedDoubleHistogramEncodingCookie = 0x0c72124f; + + private const int EncodingCookieBaseV2 = 0x1c849303; + private const int EncodingCookieBaseV1 = 0x1c849301; + private const int EncodingCookieBaseV0 = 0x1c849308; + + private const int CompressedEncodingCookieBaseV0 = 0x1c849309; + private const int CompressedEncodingCookieBaseV1 = 0x1c849302; + private const int CompressedEncodingCookieBaseV2 = 0x1c849304; + private const int EncodingHeaderSizeV0 = 32; + private const int EncodingHeaderSizeV1 = 40; + private const int EncodingHeaderSizeV2 = 40; + + private const int V2MaxWordSizeInBytes = 9; // LEB128-64b9B + ZigZag require up to 9 bytes per word + private const int Rfc1950HeaderLength = 2; + + private static readonly Type[] HistogramClassConstructorArgsTypes = { typeof(long), typeof(long), typeof(int) }; + + /// + /// Construct a new histogram by decoding it from a compressed form in a ByteBuffer. + /// + /// The buffer to decode from + /// Force highestTrackableValue to be set at least this high + /// The newly constructed histogram + public static HistogramBase DecodeFromCompressedByteBuffer(ByteBuffer buffer, long minBarForHighestTrackableValue) + { + var cookie = buffer.GetInt(); + var headerSize = GetHeaderSize(cookie); + var lengthOfCompressedContents = buffer.GetInt(); + HistogramBase histogram; + //Skip the first two bytes (from the RFC 1950 specification) and move to the deflate specification (RFC 1951) + // http://george.chiramattel.com/blog/2007/09/deflatestream-block-length-does-not-match.html + using (var inputStream = new MemoryStream(buffer.ToArray(), buffer.Position + Rfc1950HeaderLength, lengthOfCompressedContents - Rfc1950HeaderLength)) + using (var decompressor = new DeflateStream(inputStream, CompressionMode.Decompress, leaveOpen: true)) + { + var headerBuffer = ByteBuffer.Allocate(headerSize); + headerBuffer.ReadFrom(decompressor, headerSize); + histogram = DecodeFromByteBuffer(headerBuffer, minBarForHighestTrackableValue, decompressor); + } + return histogram; + } + + /// + /// Construct a new histogram by decoding it from a . + /// + /// The buffer to decode from + /// Force highestTrackableValue to be set at least this high + /// The that is being used to decompress the payload. Optional. + /// The newly constructed histogram + public static HistogramBase DecodeFromByteBuffer(ByteBuffer buffer, long minBarForHighestTrackableValue, + DeflateStream decompressor = null) + { + var header = ReadHeader(buffer); + var wordSizeInBytes = GetWordSizeInBytesFromCookie(header.Cookie); + var histogramType = GetBestTypeForWordSize(wordSizeInBytes); + var histogram = Create(histogramType, header, minBarForHighestTrackableValue); + + int expectedCapacity = Math.Min(histogram.GetNeededByteBufferCapacity() - header.CapacityEstimateExcess, header.PayloadLengthInBytes); + var payLoadSourceBuffer = PayLoadSourceBuffer(buffer, decompressor, expectedCapacity, header); + + + var filledLength = histogram.FillCountsFromBuffer(payLoadSourceBuffer, expectedCapacity, wordSizeInBytes); + histogram.EstablishInternalTackingValues(filledLength); + + return histogram; + } + + /// + /// Encode this histogram in compressed form into a . + /// + /// The histogram to encode + /// The buffer to write to + /// The number of bytes written to the buffer + public static int EncodeIntoCompressedByteBuffer(this HistogramBase source, ByteBuffer targetBuffer) + { + int neededCapacity = source.GetNeededByteBufferCapacity(); + var intermediateUncompressedByteBuffer = ByteBuffer.Allocate(neededCapacity); + source.Encode(intermediateUncompressedByteBuffer, HistogramEncoderV2.Instance); + + int initialTargetPosition = targetBuffer.Position; + targetBuffer.PutInt(GetCompressedEncodingCookie()); + int compressedContentsHeaderPosition = targetBuffer.Position; + targetBuffer.PutInt(0); // Placeholder for compressed contents length + var compressedDataLength = targetBuffer.CompressedCopy(intermediateUncompressedByteBuffer, targetBuffer.Position); + + targetBuffer.PutInt(compressedContentsHeaderPosition, compressedDataLength); // Record the compressed length + int bytesWritten = compressedDataLength + 8; + targetBuffer.Position = (initialTargetPosition + bytesWritten); + return bytesWritten; + } + + /// + /// Gets the encoding cookie for a Histogram. + /// + /// The histogram to get the cookie for + /// The integer cookie value for the histogram. + public static int GetEncodingCookie(this HistogramBase histogram) + { + //return EncodingCookieBase + (histogram.WordSizeInBytes << 4); + return EncodingCookieBaseV2 | 0x10; // LSBit of wordsize byte indicates TLZE Encoding + } + + private static IHeader ReadHeader(ByteBuffer buffer) + { + var cookie = buffer.GetInt(); + var cookieBase = GetCookieBase(cookie); + var wordsize = GetWordSizeInBytesFromCookie(cookie); + if ((cookieBase == EncodingCookieBaseV2) || (cookieBase == EncodingCookieBaseV1)) + { + if (cookieBase == EncodingCookieBaseV2) + { + if (wordsize != V2MaxWordSizeInBytes) + { + throw new ArgumentException("The buffer does not contain a Histogram (no valid cookie found)"); + } + } + return new V1Header(cookie, buffer); + } + else if (cookieBase == EncodingCookieBaseV0) + { + return new V0Header(cookie, buffer); + } + throw new NotSupportedException("The buffer does not contain a Histogram (no valid cookie found)"); + } + + private static HistogramBase Create(Type histogramType, IHeader header, long minBarForHighestTrackableValue) + { + var constructor = TypeHelper.GetConstructor(histogramType, HistogramClassConstructorArgsTypes); + if (constructor == null) + throw new ArgumentException("The target type does not have a supported constructor", nameof(histogramType)); + + var highestTrackableValue = Math.Max(header.HighestTrackableValue, minBarForHighestTrackableValue); + try + { + var histogram = (HistogramBase)constructor.Invoke(new object[] + { + header.LowestTrackableUnitValue, + highestTrackableValue, + header.NumberOfSignificantValueDigits + }); + + //TODO: Java does this now. Need to follow this through -LC + //histogram.IntegerToDoubleValueConversionRatio = header.IntegerToDoubleValueConversionRatio; + //histogram.NormalizingIndexOffset = header.NormalizingIndexOffset; + return histogram; + } + catch (Exception ex) + { + //As we are calling an unknown method (the ctor) we cant be sure of what of what type of exceptions we need to catch -LC + throw new ArgumentException("Unable to create histogram of Type " + histogramType.Name + ": " + ex.Message, ex); + } + } + + private static ByteBuffer PayLoadSourceBuffer(ByteBuffer buffer, DeflateStream decompressor, int expectedCapacity, IHeader header) + { + ByteBuffer payLoadSourceBuffer; + if (decompressor == null) + { + // No compressed source buffer. Payload is in buffer, after header. + if (expectedCapacity > buffer.Remaining()) + { + throw new ArgumentException("The buffer does not contain the full Histogram payload"); + } + payLoadSourceBuffer = buffer; + } + else + { + payLoadSourceBuffer = ByteBuffer.Allocate(expectedCapacity); + var decompressedByteCount = payLoadSourceBuffer.ReadFrom(decompressor, expectedCapacity); + + if ((header.PayloadLengthInBytes != int.MaxValue) && (decompressedByteCount < header.PayloadLengthInBytes)) + { + throw new ArgumentException("The buffer does not contain the indicated payload amount"); + } + } + return payLoadSourceBuffer; + } + + private static int GetHeaderSize(int cookie) + { + var cookieBase = GetCookieBase(cookie); + + switch (cookieBase) + { + case CompressedEncodingCookieBaseV2: + return EncodingHeaderSizeV2; + case CompressedEncodingCookieBaseV1: + return EncodingHeaderSizeV1; + case CompressedEncodingCookieBaseV0: + return EncodingHeaderSizeV0; + default: + throw new ArgumentException("The buffer does not contain a compressed Histogram"); + } + } + + private static int GetCookieBase(int cookie) + { + return (cookie & ~0xf0); + } + + private static int GetCompressedEncodingCookie() + { + return CompressedEncodingCookieBaseV2 | 0x10; // LSBit of wordsize byte indicates TLZE Encoding + } + + private static int GetWordSizeInBytesFromCookie(int cookie) + { + var cookieBase = GetCookieBase(cookie); + if (cookieBase == EncodingCookieBaseV2 || + cookieBase == CompressedEncodingCookieBaseV2) + { + return V2MaxWordSizeInBytes; + } + //V1 & V0 word size. + var sizeByte = (cookie & 0xf0) >> 4; + return sizeByte & 0xe; + } + + private static Type GetBestTypeForWordSize(int wordSizeInBytes) + { + switch (wordSizeInBytes) + { + case 2: return typeof(ShortHistogram); + case 4: return typeof(IntHistogram); + case 8: return typeof(LongHistogram); + case 9: return typeof(LongHistogram); + default: + throw new IndexOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramExtensions.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramExtensions.cs new file mode 100644 index 0000000000..2c37aa4fb5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramExtensions.cs @@ -0,0 +1,264 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using HdrHistogram.Iteration; +using HdrHistogram.Output; + +namespace HdrHistogram +{ + /// + /// Extension methods for the Histogram types. + /// + internal static class HistogramExtensions + { + + /// + /// Get the highest recorded value level in the histogram + /// + /// the Max value recorded in the histogram + public static long GetMaxValue(this HistogramBase histogram) + { + var max = histogram.RecordedValues().Select(hiv => hiv.ValueIteratedTo).LastOrDefault(); + return histogram.HighestEquivalentValue(max); + } + + /// + /// Get the computed mean value of all recorded values in the histogram + /// + /// the mean value (in value units) of the histogram data + public static double GetMean(this HistogramBase histogram) + { + var totalValue = histogram.RecordedValues().Select(hiv => hiv.TotalValueToThisValue).LastOrDefault(); + return (totalValue * 1.0) / histogram.TotalCount; + } + + /// + /// Get the computed standard deviation of all recorded values in the histogram + /// + /// the standard deviation (in value units) of the histogram data + public static double GetStdDeviation(this HistogramBase histogram) + { + var mean = histogram.GetMean(); + var geometricDeviationTotal = 0.0; + foreach (var iterationValue in histogram.RecordedValues()) + { + double deviation = (histogram.MedianEquivalentValue(iterationValue.ValueIteratedTo) * 1.0) - mean; + geometricDeviationTotal += (deviation * deviation) * iterationValue.CountAddedInThisIterationStep; + } + var stdDeviation = Math.Sqrt(geometricDeviationTotal / histogram.TotalCount); + return stdDeviation; + } + + /// + /// Get the highest value that is equivalent to the given value within the histogram's resolution. + /// Where "equivalent" means that value samples recorded for any two equivalent values are counted in a common + /// total count. + /// + /// The histogram to operate on + /// The given value + /// The highest value that is equivalent to the given value within the histogram's resolution. + public static long HighestEquivalentValue(this HistogramBase histogram, long value) + { + return histogram.NextNonEquivalentValue(value) - 1; + } + + /// + /// Copy this histogram into the target histogram, overwriting it's contents. + /// + /// The source histogram + /// the histogram to copy into + public static void CopyInto(this HistogramBase source, HistogramBase targetHistogram) + { + targetHistogram.Reset(); + targetHistogram.Add(source); + targetHistogram.StartTimeStamp = source.StartTimeStamp; + targetHistogram.EndTimeStamp = source.EndTimeStamp; + } + + /// + /// Provide a means of iterating through histogram values according to percentile levels. + /// The iteration is performed in steps that start at 0% and reduce their distance to 100% according to the + /// parameter, ultimately reaching 100% when all recorded + /// histogram values are exhausted. + /// + /// The histogram to operate on + /// + /// The number of iteration steps per half-distance to 100%. + /// + /// + /// An enumerator of through the histogram using a + /// . + /// + public static IEnumerable Percentiles(this HistogramBase histogram, int percentileTicksPerHalfDistance) + { + return new PercentileEnumerable(histogram, percentileTicksPerHalfDistance); + } + + /// + /// Produce textual representation of the value distribution of histogram data by percentile. + /// The distribution is output with exponentially increasing resolution, with each exponentially decreasing + /// half-distance containing percentile reporting tick points. + /// + /// The histogram to operate on + /// The into which the distribution will be output + /// + /// The number of reporting points per exponentially decreasing half-distance + /// + /// + /// The scaling factor by which to divide histogram recorded values units in output. + /// Use the constant values to help choose an appropriate output measurement. + /// + /// Output in CSV (Comma Separated Values) format if true, else use plain text form. + public static void OutputPercentileDistribution(this HistogramBase histogram, + TextWriter writer, + int percentileTicksPerHalfDistance = 5, + double outputValueUnitScalingRatio = OutputScalingFactor.None, + bool useCsvFormat = false) + { + var formatter = useCsvFormat + ? (IOutputFormatter)new CsvOutputFormatter(writer, histogram.NumberOfSignificantValueDigits, outputValueUnitScalingRatio) + : (IOutputFormatter)new HgrmOutputFormatter(writer, histogram.NumberOfSignificantValueDigits, outputValueUnitScalingRatio); + + try + { + formatter.WriteHeader(); + foreach (var iterationValue in histogram.Percentiles(percentileTicksPerHalfDistance)) + { + formatter.WriteValue(iterationValue); + } + formatter.WriteFooter(histogram); + } + catch (ArgumentOutOfRangeException) + { + // Overflow conditions on histograms can lead to ArgumentOutOfRangeException on iterations: + if (histogram.HasOverflowed()) + { + writer.Write("# Histogram counts indicate OVERFLOW values"); + } + else + { + // Re-throw if reason is not a known overflow: + throw; + } + } + } + + /// + /// Executes the action and records the time to complete the action. + /// The time is recorded in system clock ticks. + /// This time may vary between frameworks and platforms, but is equivalent to (1/Stopwatch.Frequency) seconds. + /// Note this is a convenience method and can carry a cost. + /// If the delegate is not cached, then it may incur an allocation cost for each invocation of + /// + /// The instance to record the latency in. + /// The functionality to execute and measure + /// + /// + /// The units returned from Stopwatch.GetTimestamp() are used as the unit of + /// recording here as they are the smallest unit that .NET can measure and require no + /// conversion at time of recording. + /// Instead conversion (scaling) can be done at time of output to microseconds, milliseconds, + /// seconds or other appropriate unit. + /// These units are sometimes referred to as ticks, but should not not to be confused with + /// ticks used in or . + /// + /// + /// If you are able to cache the delegate, then doing so is encouraged. + /// + /// Here are two examples. + /// The first does not cache the delegate + /// + /// + /// for (long i = 0; i < loopCount; i++) + /// { + /// histogram.Record(IncrementNumber); + /// } + /// + /// This second example does cache the delegate + /// + /// Action incrementNumber = IncrementNumber; + /// for (long i = 0; i < loopCount; i++) + /// { + /// histogram.Record(incrementNumber); + /// } + /// + /// In the second example, we will not be making allocations each time i.e. an allocation of an from IncrementNumber. + /// This will reduce memory pressure and therefore garbage collection penalties. + /// For performance sensitive applications, this method may not be suitable. + /// As always, you are encouraged to test and measure the impact for your scenario. + /// + /// + /// + public static void Record(this IRecorder recorder, Action action) + { + var start = Stopwatch.GetTimestamp(); + action(); + var elapsed = Stopwatch.GetTimestamp() - start; + recorder.RecordValue(elapsed); + } + + /// + /// Records the elapsed time till the returned token is disposed. + /// This can be useful to testing large blocks of code, or wrapping around and await clause. + /// + /// The instance to record the latency in. + /// Returns a token to be disposed once the scope + /// + /// This can be helpful for recording a scope of work. + /// It also has the benefit of allowing a simple way to record an awaitable method. + /// + /// This example shows how an awaitable method can be cleanly instrumented using C# using scope. + /// + /// using(recorder.RecordScope()) + /// { + /// await SomeExpensiveCall(); + /// } + /// + /// + /// It should be noted that this method returns a token and as such allocates an object. + /// This should taken into consideration, specifically the cost of the allocation and GC would affect the program. + /// + public static IDisposable RecordScope(this IRecorder recorder) + { + return new Timer(recorder); + } + + private sealed class Timer : IDisposable + { + private readonly IRecorder _recorder; + private readonly long _start; + + public Timer(IRecorder recorder) + { + _recorder = recorder; + _start = Stopwatch.GetTimestamp(); + } + + public void Dispose() + { + var elapsed = Stopwatch.GetTimestamp() - _start; + _recorder.RecordValue(elapsed); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramFactoryDelegate.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramFactoryDelegate.cs new file mode 100644 index 0000000000..1b952a837b --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramFactoryDelegate.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +namespace HdrHistogram +{ + /// + /// The method definition for a histogram factory. + /// + /// The instance id the histogram should be created with. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + internal delegate HistogramBase HistogramFactoryDelegate( + long instanceId, long lowestDiscernibleValue, long highestTrackableValue, int numberOfSignificantValueDigits); +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramLogReader.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramLogReader.cs new file mode 100644 index 0000000000..89df7445eb --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramLogReader.cs @@ -0,0 +1,310 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// Reads a log of Histograms from the provided . + /// + internal sealed class HistogramLogReader : IDisposable, IHistogramLogV1Reader + { + //TODO: I think this should be able to cater for 0dp -LC + private static readonly Regex StartTimeMatcher = new Regex(@"#\[StartTime: (?\d*\.\d{1,3}) ", RegexOptions.Compiled); + private static readonly Regex BaseTimeMatcher = new Regex(@"#\[BaseTime: (?\d*\.\d{1,3}) ", RegexOptions.Compiled); + //Content lines - format = startTimestamp, intervalLength, maxTime, histogramPayload + private static readonly Regex UntaggedLogLineMatcher = new Regex(@"(?\d*\.\d*),(?\d*\.\d*),(?\d*\.\d*),(?.*)", RegexOptions.Compiled); + private static readonly Regex TaggedLogLineMatcher = new Regex(@"((?Tag=.+),)?(?\d*\.\d*),(?\d*\.\d*),(?\d*\.\d*),(?.*)", RegexOptions.Compiled); + private readonly TextReader _log; + private double _startTimeInSeconds; + + /// + /// Reads each histogram out from the underlying stream. + /// + /// The to read from. + /// Return a lazily evaluated sequence of histograms. + public static IEnumerable Read(Stream inputStream) + { + using (var reader = new HistogramLogReader(inputStream)) + { + return reader.ReadHistograms(); + } + } + + /// + /// Creates a that reads from the provided . + /// + /// The to read from. + public HistogramLogReader(Stream inputStream) + { + _log = new StreamReader(inputStream, System.Text.Encoding.UTF8, true, 1024, true); + } + + /// + /// Reads each histogram out from the underlying stream. + /// + /// Return a lazily evaluated sequence of histograms. + public IEnumerable ReadHistograms() + { + _startTimeInSeconds = 0; + double baseTimeInSeconds = 0; + bool hasStartTime = false; + bool hasBaseTime = false; + foreach (var line in ReadLines()) + { + //Comments (and header metadata) + if (IsComment(line)) + { + if (IsStartTime(line)) + { + _startTimeInSeconds = ParseStartTime(line); + hasStartTime = true; + } + else if (IsBaseTime(line)) + { + baseTimeInSeconds = ParseBaseTime(line); + hasBaseTime = true; + } + } + //Legend/Column headers + else if (IsLegend(line)) + { + //Ignore + } + + else + { + //Content lines - format = startTimestamp, intervalLength, maxTime, histogramPayload + + var match = TaggedLogLineMatcher.Match(line); + var tag = ParseTag(match.Groups["tag"].Value); + var logTimeStampInSec = ParseDouble(match, "startTime"); + var intervalLength = ParseDouble(match, "interval"); + var maxTime = ParseDouble(match, "max"); //Ignored as it can be inferred -LC + var payload = match.Groups["payload"].Value; + + if (!hasStartTime) + { + // No explicit start time noted. Use 1st observed time: + _startTimeInSeconds = logTimeStampInSec; + hasStartTime = true; + } + if (!hasBaseTime) + { + // No explicit base time noted. Deduce from 1st observed time (compared to start time): + if (logTimeStampInSec < _startTimeInSeconds - (365 * 24 * 3600.0)) + //if (UnixTimeExtensions.ToDateFromSecondsSinceEpoch(logTimeStampInSec) < startTime.AddYears(1)) + { + // Criteria Note: if log timestamp is more than a year in the past (compared to + // StartTime), we assume that timestamps in the log are not absolute + baseTimeInSeconds = _startTimeInSeconds; + } + else + { + // Timestamps are absolute + baseTimeInSeconds = 0.0; + } + hasBaseTime = true; + } + + double absoluteStartTimeStampSec = logTimeStampInSec + baseTimeInSeconds; + double absoluteEndTimeStampSec = absoluteStartTimeStampSec + intervalLength; + + + byte[] bytes = Convert.FromBase64String(payload); + var buffer = ByteBuffer.Allocate(bytes); + var histogram = DecodeHistogram(buffer, 0); + histogram.Tag = tag; + histogram.StartTimeStamp = (long)(absoluteStartTimeStampSec * 1000.0); + histogram.EndTimeStamp = (long)(absoluteEndTimeStampSec * 1000.0); + yield return histogram; + } + } + } + + IEnumerable IHistogramLogV1Reader.ReadHistograms() + { + _startTimeInSeconds = 0; + double baseTimeInSeconds = 0; + bool hasStartTime = false; + bool hasBaseTime = false; + foreach (var line in ReadLines()) + { + //Comments (and header metadata) + if (IsComment(line)) + { + if (IsStartTime(line)) + { + _startTimeInSeconds = ParseStartTime(line); + hasStartTime = true; + } + else if (IsBaseTime(line)) + { + baseTimeInSeconds = ParseBaseTime(line); + hasBaseTime = true; + } + } + //Legend/Column headers + else if (IsV1Legend(line)) + { + //Ignore + } + + else + { + //Content lines - format = startTimestamp, intervalLength, maxTime, histogramPayload + + var match = UntaggedLogLineMatcher.Match(line); + var logTimeStampInSec = ParseDouble(match, "startTime"); + var intervalLength = ParseDouble(match, "interval"); + var maxTime = ParseDouble(match, "max"); //Ignored as it can be inferred -LC + var payload = match.Groups["payload"].Value; + + + if (!hasStartTime) + { + // No explicit start time noted. Use 1st observed time: + _startTimeInSeconds = logTimeStampInSec; + hasStartTime = true; + } + if (!hasBaseTime) + { + // No explicit base time noted. Deduce from 1st observed time (compared to start time): + if (logTimeStampInSec < _startTimeInSeconds - (365 * 24 * 3600.0)) + { + // Criteria Note: if log timestamp is more than a year in the past (compared to + // StartTime), we assume that timestamps in the log are not absolute + baseTimeInSeconds = _startTimeInSeconds; + } + else + { + // Timestamps are absolute + baseTimeInSeconds = 0.0; + } + hasBaseTime = true; + } + + double absoluteStartTimeStampSec = logTimeStampInSec + baseTimeInSeconds; + double absoluteEndTimeStampSec = absoluteStartTimeStampSec + intervalLength; + + + byte[] bytes = Convert.FromBase64String(payload); + var buffer = ByteBuffer.Allocate(bytes); + var histogram = DecodeHistogram(buffer, 0); + histogram.StartTimeStamp = (long)(absoluteStartTimeStampSec * 1000.0); + histogram.EndTimeStamp = (long)(absoluteEndTimeStampSec * 1000.0); + yield return histogram; + } + } + } + + /// + /// Gets the start time for the set of Histograms. + /// + /// Either the explicit encoded start time, or falls back to the start time of the first histogram. + /// + /// The current implementation requires the consumer to only use this after enumerating the Histograms from the method. + /// + public DateTime GetStartTime() + { + //NOTE: It would be good if this could expose a DateTimeOffset, however currently that would be misleading, as that level of fidelity is not captured. -LC + + //If StartTime was set (#[StartTime:...) use it, else use the first `logTimestampInSec` + //This method is odd, in that it only works if the file has been read. That is a bit messy. -LC + + //TODO: Create an API that allows GetStartTime to be deterministic (not dependant on how far through ReadHistograms you have enumerated. + + return _startTimeInSeconds.ToDateFromSecondsSinceEpoch(); + } + + private static HistogramBase DecodeHistogram(ByteBuffer buffer, long minBarForHighestTrackableValue) + { + return HistogramEncoding.DecodeFromCompressedByteBuffer(buffer, minBarForHighestTrackableValue); + } + + private IEnumerable ReadLines() + { + while (true) + { + var line = _log.ReadLine(); + if (line == null) + yield break; + yield return line; + } + } + + private static bool IsComment(string line) + { + return line.StartsWith("#"); + } + + private static bool IsStartTime(string line) + { + return line.StartsWith("#[StartTime: "); + } + + private static bool IsBaseTime(string line) + { + return line.StartsWith("#[BaseTime: "); + } + + private static bool IsLegend(string line) + { + var legend = "\"StartTimestamp\",\"Interval_Length\",\"Interval_Max\",\"Interval_Compressed_Histogram\""; + return line.Equals(legend); + } + private static bool IsV1Legend(string line) + { + var legend = "\"StartTimestamp\",\"EndTimestamp\",\"Interval_Max\",\"Interval_Compressed_Histogram\""; + return line.Equals(legend); + } + + private static string ParseTag(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + value = value.Substring(4); + if (string.IsNullOrWhiteSpace(value)) + return null; + return value; + } + + private static double ParseStartTime(string line) + { + var match = StartTimeMatcher.Match(line); + return ParseDouble(match, "seconds"); + } + + private static double ParseBaseTime(string line) + { + var match = BaseTimeMatcher.Match(line); + return ParseDouble(match, "seconds"); + } + + private static double ParseDouble(Match match, string group) + { + var value = match.Groups[group].Value; + return double.Parse(value); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + using (_log) { } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramLogWriter.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramLogWriter.cs new file mode 100644 index 0000000000..cac08a2871 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/HistogramLogWriter.cs @@ -0,0 +1,150 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.IO; +using System.Threading; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// Writes zero, one or many instances to a . + /// + /// + internal sealed class HistogramLogWriter : IDisposable + { + private const string HistogramLogFormatVersion = "1.3"; + + private readonly TextWriter _log; + private bool _hasHeaderWritten = false; + private int _isDisposed = 0; + + /// + /// Writes the provided histograms to the underlying with a given overall start time. + /// + /// The to write to. + /// The start time of the set of histograms. + /// The histograms to include in the output. + public static void Write(Stream outputStream, DateTime startTime, params HistogramBase[] histograms) + { + using (var writer = new HistogramLogWriter(outputStream)) + { + writer.Write(startTime, histograms); + } + } + + /// + /// Creates a that writes to an underlying . + /// + /// + /// The stream to write to. + /// The stream is left open for the consumer to close. + /// + public HistogramLogWriter(Stream outputStream) + { + _log = new StreamWriter(outputStream, System.Text.Encoding.Unicode, 1024, true) + { + NewLine = "\n" + }; + } + + /// + /// Writes the provided histograms to the underlying with a given overall start time. + /// + /// The start time of the set of histograms. + /// The histograms to include in the output. + public void Write(DateTime startTime, params HistogramBase[] histograms) + { + WriteLogFormatVersion(); + WriteStartTime(startTime); + WriteLegend(); + _hasHeaderWritten = true; + foreach (var histogram in histograms) + { + WriteHistogram(histogram); + } + } + + /// + /// Appends a Histogram to the log. + /// + /// The histogram to write to the log. + public void Append(HistogramBase histogram) + { + if (!_hasHeaderWritten) + { + Write(histogram.StartTimeStamp.ToDateFromMillisecondsSinceEpoch(), histogram); + } + else + { + WriteHistogram(histogram); + } + } + + /// + /// Output a log format version to the log. + /// + private void WriteLogFormatVersion() + { + _log.WriteLine($"#[Histogram log format version {HistogramLogFormatVersion}]"); + _log.Flush(); + } + + /// + /// Log a start time in the log. + /// + /// Time the log was started. + private void WriteStartTime(DateTime startTimeWritten) + { + var secondsSinceEpoch = startTimeWritten.SecondsSinceUnixEpoch(); + _log.WriteLine($"#[StartTime: {secondsSinceEpoch:F3} (seconds since epoch), {startTimeWritten:o}]"); + _log.Flush(); + } + + private void WriteLegend() + { + _log.WriteLine("\"StartTimestamp\",\"Interval_Length\",\"Interval_Max\",\"Interval_Compressed_Histogram\""); + _log.Flush(); + } + + private void WriteHistogram(HistogramBase histogram) + { + var targetBuffer = ByteBuffer.Allocate(histogram.GetNeededByteBufferCapacity()); + var compressedLength = histogram.EncodeIntoCompressedByteBuffer(targetBuffer); + byte[] compressedArray = new byte[compressedLength]; + targetBuffer.BlockGet(compressedArray, 0, 0, compressedLength); + + var startTimeStampSec = histogram.StartTimeStamp / 1000.0; + var endTimeStampSec = histogram.EndTimeStamp / 1000.0; + var intervalLength = endTimeStampSec - startTimeStampSec; + var maxValueUnitRatio = 1000000.0; + var intervalMax = histogram.GetMaxValue() / maxValueUnitRatio; + + var binary = Convert.ToBase64String(compressedArray); + var payload = histogram.Tag == null + ? $"{startTimeStampSec:F3},{intervalLength:F3},{intervalMax:F3},{binary}" + : $"Tag={histogram.Tag},{startTimeStampSec:F3},{intervalLength:F3},{intervalMax:F3},{binary}"; + _log.WriteLine(payload); + _log.Flush(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0) + { + using (_log) { } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IHistogramLogV1Reader.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IHistogramLogV1Reader.cs new file mode 100644 index 0000000000..6fcb1f2e09 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IHistogramLogV1Reader.cs @@ -0,0 +1,26 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; + +namespace HdrHistogram +{ + /// + /// Defines a method for reading Histogram logs in the v1 format. + /// + internal interface IHistogramLogV1Reader + { + /// + /// Reads a v1 formatted histogram log. + /// + /// Returns a sequence of items. + IEnumerable ReadHistograms(); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IRecorder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IRecorder.cs new file mode 100644 index 0000000000..45a66ec992 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IRecorder.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +namespace HdrHistogram +{ + /// + /// Provides methods to record values. + /// + internal interface IRecorder + { + /// + /// Records a value in the histogram + /// + /// The value to be recorded + /// if value is exceeds highestTrackableValue + void RecordValue(long value); + + /// + /// Record a value in the histogram (adding to the value's current count) + /// + /// The value to be recorded + /// The number of occurrences of this value to record + /// if value is exceeds highestTrackableValue + void RecordValueWithCount(long value, long count); + + /// + /// Record a value in the histogram. + /// + /// The value to record + /// If is larger than 0, add auto-generated value records as appropriate if is larger than + /// if value is exceeds highestTrackableValue + /// + /// To compensate for the loss of sampled values when a recorded value is larger than the expected interval between value samples, + /// Histogram will auto-generate an additional series of decreasingly-smaller (down to the expectedIntervalBetweenValueSamples) value records. + /// + /// Note: This is a at-recording correction method, as opposed to the post-recording correction method provided by currently unimplemented CopyCorrectedForCoordinatedOmission method. + /// The two methods are mutually exclusive, and only one of the two should be be used on a given data set to correct for the same coordinated omission issue. + /// + /// See notes in the description of the Histogram calls for an illustration of why this corrective behavior is important. + /// + void RecordValueWithExpectedInterval(long value, long expectedIntervalBetweenValueSamples); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IntConcurrentHistogram.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IntConcurrentHistogram.cs new file mode 100644 index 0000000000..5ad6fe3b30 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/IntConcurrentHistogram.cs @@ -0,0 +1,232 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Diagnostics; +using System.Threading; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// An integer values High Dynamic Range (HDR) Histogram that supports safe concurrent recording operations. + /// + /// + /// A guarantees lossless recording of values into the histogram even when the histogram is updated by multiple threads. + ///

+ /// It is important to note that concurrent recording is the only thread-safe behavior provided by . + /// It provides no implicit synchronization that would prevent the contents of the histogram from changing during other operations. + /// These non-synchronised operations include queries, iterations, copies, or addition operations on the histogram. + /// Concurrent updates that would safely work in the presence of queries, copies, or additions of histogram objects should use the which is intended for this purpose. + ///

+ ///
+ internal class IntConcurrentHistogram : HistogramBase + { + private readonly WriterReaderPhaser _wrp = new WriterReaderPhaser(); + private readonly AtomicIntArray _counts; + private long _totalCount = 0L; + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// Providing a is useful is situations where the units used for the histogram's values are much smaller that the minimal accuracy required. + /// For example when tracking time values stated in nanoseconds, where the minimal accuracy required is a microsecond, the proper value for would be 1000. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + public IntConcurrentHistogram(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + : base(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new AtomicIntArray(CountsArrayLength); + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// An identifier for this instance. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in ticks (100 nanoseconds), where the minimal accuracy required is a + /// microsecond, the proper value for lowestTrackableValue would be 10. + /// + public IntConcurrentHistogram(long instanceId, long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + : base(instanceId, lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new AtomicIntArray(CountsArrayLength); + } + + /// + /// Gets the total number of recorded values. + /// + public override long TotalCount + { + get {return Interlocked.Read(ref _totalCount);} + protected set { Interlocked.Exchange(ref _totalCount, value); } + } + + /// + /// Returns the word size of this implementation + /// + protected override int WordSizeInBytes => sizeof(int); + + /// + /// The maximum value a count can be for any given bucket. + /// + protected override long MaxAllowableCount => long.MaxValue; + + /// + /// Copies the data from this instance to a new instance. + /// + /// A new copy of this instance. + public override HistogramBase Copy() + { + var copy = new IntConcurrentHistogram(InstanceId, LowestTrackableValue, HighestTrackableValue, NumberOfSignificantValueDigits); + copy.Add(this); + return copy; + } + + /// + /// Gets the number of recorded values at a given index. + /// + /// The index to get the count for + /// The number of recorded values at the given index. + protected override long GetCountAtIndex(int index) + { + try + { + _wrp.ReaderLock(); + + Debug.Assert(base.CountsArrayLength == _counts.Length); + + return _counts[index]; + } + finally + { + _wrp.ReaderUnlock(); + } + } + + /// + /// Sets the count at the given index. + /// + /// The index to be set + /// The value to set + protected override void SetCountAtIndex(int index, long value) + { + //At time of writing, this was unused. + // This method is only used in decoding from byte array, in which case the basic histogram implementations would be used. + // You can then Add these decoded instances to this Concurrent implementation. + + //Throwing as this code is currently untested. + throw new NotImplementedException(); + //try + //{ + // _wrp.ReaderLock(); + // Debug.Assert(CountsArrayLength == _activeCounts.Length); + // Debug.Assert(CountsArrayLength == _inactiveCounts.Length); + // var idx = NormalizeIndex(index, _activeCounts.NormalizingIndexOffset, _activeCounts.Length); + // _activeCounts.LazySet(idx, value); + // idx = NormalizeIndex(index, _inactiveCounts.NormalizingIndexOffset, _inactiveCounts.Length); + // _inactiveCounts.LazySet(idx, 0); + //} + //finally + //{ + // _wrp.ReaderUnlock(); + //} + } + + /// + /// Increments the count at the given index. Will also increment the . + /// + /// The index to increment the count at. + protected override void IncrementCountAtIndex(int index) + { + long criticalValue = _wrp.WriterCriticalSectionEnter(); + try + { + _counts.IncrementAndGet(index); + Interlocked.Increment(ref _totalCount); + } + finally + { + _wrp.WriterCriticalSectionExit(criticalValue); + } + } + + /// + /// Adds the specified amount to the count of the provided index. Also increments the by the same amount. + /// + /// The index to increment. + /// The amount to increment by. + protected override void AddToCountAtIndex(int index, long addend) + { + long criticalValue = _wrp.WriterCriticalSectionEnter(); + try + { + _counts.AddAndGet(index, (int)addend); + Interlocked.Add(ref _totalCount, addend); + } + finally + { + _wrp.WriterCriticalSectionExit(criticalValue); + } + } + + /// + /// Clears the counts of this implementation. + /// + protected override void ClearCounts() + { + try + { + _wrp.ReaderLock(); + Debug.Assert(CountsArrayLength == _counts.Length); + for (int i = 0; i<_counts.Length; i++) + { + _counts[i] = 0; + } + TotalCount = 0; + } + finally + { + _wrp.ReaderUnlock(); + } + } + + /// + /// Copies the internal counts array into the supplied array. + /// + /// The array to write each count value into. + protected override void CopyCountsInto(long[] target) + { + for (int i = 0; i + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; + +namespace HdrHistogram +{ + /// + /// A High Dynamic Range (HDR) Histogram using an count type. + /// + /// + /// Histogram supports the recording and analyzing sampled data value counts across a configurable integer value + /// range with configurable value precision within the range. + /// Value precision is expressed as the number of significant digits in the value recording, and provides control + /// over value quantization behavior across the value range and the subsequent value resolution at any given level. + /// + /// For example, a Histogram could be configured to track the counts of observed integer values between 0 and + /// 36,000,000,000 while maintaining a value precision of 3 significant digits across that range. + /// Value quantization within the range will thus be no larger than 1/1,000th (or 0.1%) of any value. + /// This example Histogram could be used to track and analyze the counts of observed response times ranging between + /// 100 nanoseconds and 1 hour in magnitude, while maintaining a value resolution of 100 nanosecond up to + /// 100 microseconds, a resolution of 1 millisecond(or better) up to one second, and a resolution of 1 second + /// (or better) up to 1,000 seconds. + /// At it's maximum tracked value(1 hour), it would still maintain a resolution of 3.6 seconds (or better). + /// + /// Histogram tracks value counts in fields. + /// Other field types are available in the and + /// implementations of . + /// + internal class IntHistogram : HistogramBase + { + private readonly int[] _counts; + + private long _totalCount; + + /// + /// Construct an given the highest value to be tracked and a number of significant decimal digits. + /// The histogram will be constructed to implicitly track(distinguish from 0) values as low as 1. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= 2. + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation.Must be a non-negative integer between 0 and 5. + public IntHistogram(long highestTrackableValue, int numberOfSignificantValueDigits) + : this(1, highestTrackableValue, numberOfSignificantValueDigits) + { + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// Providing a is useful is situations where the units used for the histogram's values are much smaller that the minimal accuracy required. + /// For example when tracking time values stated in nanoseconds, where the minimal accuracy required is a microsecond, the proper value for would be 1000. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation.Must be a non-negative integer between 0 and 5. + public IntHistogram(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + : base(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new int[CountsArrayLength]; + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// An identifier for this instance. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in ticks (100 nanoseconds), where the minimal accuracy required is a + /// microsecond, the proper value for lowestTrackableValue would be 10. + /// + public IntHistogram(long instanceId, long lowestTrackableValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + : base(instanceId, lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new int[CountsArrayLength]; + } + + + + /// + /// Gets the total number of recorded values. + /// + public override long TotalCount { get { return _totalCount; } protected set { _totalCount = value; } } + + /// + /// Returns the word size of this implementation + /// + protected override int WordSizeInBytes => 4; + + /// + /// The maximum value a count can be for any given bucket. + /// + protected override long MaxAllowableCount => int.MaxValue; + + /// + /// Create a copy of this histogram, complete with data and everything. + /// + /// A distinct copy of this histogram. + public override HistogramBase Copy() + { + var copy = new IntHistogram(LowestTrackableValue, HighestTrackableValue, NumberOfSignificantValueDigits); + copy.Add(this); + return copy; + } + + /// + /// Gets the number of recorded values at a given index. + /// + /// The index to get the count for + /// The number of recorded values at the given index. + protected override long GetCountAtIndex(int index) + { + return _counts[index]; + } + + /// + /// Sets the count at the given index. + /// + /// The index to be set + /// The value to set + protected override void SetCountAtIndex(int index, long value) + { + _counts[index] = (int)value; + } + + /// + /// Increments the count at the given index. Will also increment the . + /// + /// The index to increment the count at. + protected override void IncrementCountAtIndex(int index) + { + _counts[index]++; + _totalCount++; + } + + /// + /// Adds the specified amount to the count of the provided index. Also increments the by the same amount. + /// + /// The index to increment. + /// The amount to increment by. + protected override void AddToCountAtIndex(int index, long addend) + { + _counts[index] += (int)addend; + _totalCount += addend; + } + + /// + /// Clears the counts of this implementation. + /// + protected override void ClearCounts() + { + Array.Clear(_counts, 0, _counts.Length); + _totalCount = 0; + } + + /// + /// Copies the internal counts array into the supplied array. + /// + /// The array to write each count value into. + protected override void CopyCountsInto(long[] target) + { + for (int i = 0; i < target.Length; i++) + { + target[i] = _counts[i]; + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AbstractHistogramEnumerator.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AbstractHistogramEnumerator.cs new file mode 100644 index 0000000000..e3f9be36de --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AbstractHistogramEnumerator.cs @@ -0,0 +1,190 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace HdrHistogram.Iteration +{ + /// + /// Provide functionality for enumerating over histogram counts. + /// + internal abstract class AbstractHistogramEnumerator : IEnumerator + { + private readonly long _savedHistogramTotalRawCount; + private readonly HistogramIterationValue _currentIterationValue; + private int _nextBucketIndex; + private int _nextSubBucketIndex; + private long _prevValueIteratedTo; + private long _totalCountToPrevIndex; + private long _totalValueToCurrentIndex; + private bool _freshSubBucket; + private long _currentValueAtIndex; + private long _nextValueAtIndex; + + protected HistogramBase SourceHistogram { get; } + protected long ArrayTotalCount { get; } + protected int CurrentBucketIndex { get; private set; } + protected int CurrentSubBucketIndex { get; private set; } + protected long TotalCountToCurrentIndex { get; private set; } + protected long CountAtThisValue { get; private set; } + + public HistogramIterationValue Current { get; private set; } + + protected AbstractHistogramEnumerator(HistogramBase histogram) + { + SourceHistogram = histogram; + _savedHistogramTotalRawCount = histogram.TotalCount; + ArrayTotalCount = histogram.TotalCount; + CurrentBucketIndex = 0; + CurrentSubBucketIndex = 0; + _currentValueAtIndex = 0; + _nextBucketIndex = 0; + _nextSubBucketIndex = 1; + _nextValueAtIndex = 1; + _prevValueIteratedTo = 0; + _totalCountToPrevIndex = 0; + TotalCountToCurrentIndex = 0; + _totalValueToCurrentIndex = 0; + CountAtThisValue = 0; + _freshSubBucket = true; + _currentIterationValue = new HistogramIterationValue(); + } + + /// + /// Returns true if the iteration has more elements. (In other words, returns true if next would return an element rather than throwing an exception.) + /// + /// true if the iterator has more elements. + protected virtual bool HasNext() + { + if (SourceHistogram.TotalCount != _savedHistogramTotalRawCount) + { + throw new InvalidOperationException("Source has been modified during enumeration."); + } + return (TotalCountToCurrentIndex < ArrayTotalCount); + } + + protected abstract void IncrementIterationLevel(); + + protected abstract bool ReachedIterationLevel(); + + protected virtual double GetPercentileIteratedTo() + { + return (100.0 * TotalCountToCurrentIndex) / ArrayTotalCount; + } + + protected virtual long GetValueIteratedTo() + { + return SourceHistogram.HighestEquivalentValue(_currentValueAtIndex); + } + + /// + /// Returns the next element in the iteration. + /// + /// the associated with the next element in the iteration. + private HistogramIterationValue Next() + { + // Move through the sub buckets and buckets until we hit the next reporting level: + while (!ExhaustedSubBuckets()) + { + CountAtThisValue = SourceHistogram.GetCountAt(CurrentBucketIndex, CurrentSubBucketIndex); + if (_freshSubBucket) + { + // Don't add unless we've incremented since last bucket... + TotalCountToCurrentIndex += CountAtThisValue; + _totalValueToCurrentIndex += CountAtThisValue * SourceHistogram.MedianEquivalentValue(_currentValueAtIndex); + _freshSubBucket = false; + } + if (ReachedIterationLevel()) + { + var valueIteratedTo = GetValueIteratedTo(); + _currentIterationValue.Set( + valueIteratedTo, + _prevValueIteratedTo, + CountAtThisValue, + (TotalCountToCurrentIndex - _totalCountToPrevIndex), + TotalCountToCurrentIndex, + _totalValueToCurrentIndex, + ((100.0 * TotalCountToCurrentIndex) / ArrayTotalCount), + GetPercentileIteratedTo()); + _prevValueIteratedTo = valueIteratedTo; + _totalCountToPrevIndex = TotalCountToCurrentIndex; + // move the next iteration level forward: + IncrementIterationLevel(); + if (SourceHistogram.TotalCount != _savedHistogramTotalRawCount) + { + throw new InvalidOperationException("Source has been modified during enumeration."); + } + return _currentIterationValue; + } + IncrementSubBucket(); + } + // Should not reach here. But possible for overflowed histograms under certain conditions + throw new ArgumentOutOfRangeException(); + } + + private bool ExhaustedSubBuckets() + { + return (CurrentBucketIndex >= SourceHistogram.BucketCount); + } + + private void IncrementSubBucket() + { + _freshSubBucket = true; + // Take on the next index: + CurrentBucketIndex = _nextBucketIndex; + CurrentSubBucketIndex = _nextSubBucketIndex; + _currentValueAtIndex = _nextValueAtIndex; + // Figure out the next next index: + _nextSubBucketIndex++; + if (_nextSubBucketIndex >= SourceHistogram.SubBucketCount) + { + _nextSubBucketIndex = SourceHistogram.SubBucketHalfCount; + _nextBucketIndex++; + } + _nextValueAtIndex = SourceHistogram.ValueFromIndex(_nextBucketIndex, _nextSubBucketIndex); + } + + #region IEnumerator explicit implementation + + object IEnumerator.Current => Current; + + bool IEnumerator.MoveNext() + { + var canMove = HasNext(); + if (canMove) + { + Current = Next(); + } + return canMove; + } + + void IEnumerator.Reset() + { + //throw new NotImplementedException(); + } + + void IDisposable.Dispose() + { + //throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AllValueEnumerable.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AllValueEnumerable.cs new file mode 100644 index 0000000000..a11858ef63 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AllValueEnumerable.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System.Collections; +using System.Collections.Generic; + +namespace HdrHistogram.Iteration +{ + /// + /// An enumerator of through the histogram using a + /// + internal sealed class AllValueEnumerable : IEnumerable + { + private readonly HistogramBase _histogram; + + /// + /// The constructor for the + /// + /// The to enumerate the values from. + public AllValueEnumerable(HistogramBase histogram) + { + _histogram = histogram; + } + + public IEnumerator GetEnumerator() + { + return new AllValuesEnumerator(_histogram); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AllValuesEnumerator.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AllValuesEnumerator.cs new file mode 100644 index 0000000000..44666dfcd0 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/AllValuesEnumerator.cs @@ -0,0 +1,53 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +namespace HdrHistogram.Iteration +{ + /// + /// Used for iterating through histogram values using the finest granularity steps supported by the underlying + /// representation.The iteration steps through all possible unit value levels, regardless of whether or not + /// there were recorded values for that value level, and terminates when all recorded histogram values are exhausted. + /// + internal sealed class AllValuesEnumerator : AbstractHistogramEnumerator + { + private int _visitedSubBucketIndex; + private int _visitedBucketIndex; + + /// + /// Constructor for the . + /// + /// The histogram this iterator will operate on + public AllValuesEnumerator(HistogramBase histogram):base(histogram) + { + _visitedSubBucketIndex = -1; + _visitedBucketIndex = -1; + } + + protected override void IncrementIterationLevel() + { + _visitedSubBucketIndex = CurrentSubBucketIndex; + _visitedBucketIndex = CurrentBucketIndex; + } + + protected override bool ReachedIterationLevel() + { + return (_visitedSubBucketIndex != CurrentSubBucketIndex) + || (_visitedBucketIndex != CurrentBucketIndex); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/HistogramIterationValue.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/HistogramIterationValue.cs new file mode 100644 index 0000000000..78da012be7 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/HistogramIterationValue.cs @@ -0,0 +1,117 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; + +namespace HdrHistogram.Iteration +{ + /// + /// Represents a value point iterated through in a Histogram, with associated stats. + /// + internal sealed class HistogramIterationValue + { + /// + /// The actual value level that was iterated to by the iterator + /// + public long ValueIteratedTo { get; private set; } + + /// + /// The actual value level that was iterated from by the iterator + /// + public long ValueIteratedFrom { get; private set; } + + /// + /// The count of recorded values in the histogram that exactly match this [lowestEquivalentValue(valueIteratedTo)...highestEquivalentValue(valueIteratedTo)] value range. + /// + public long CountAtValueIteratedTo { get; private set; } + + /// + /// The count of recorded values in the histogram that were added to the totalCountToThisValue(below) as a result on this iteration step. Since multiple iteration steps may occur with overlapping equivalent value ranges, the count may be lower than the count found at the value (e.g.multiple linear steps or percentile levels can occur within a single equivalent value range) + /// + public long CountAddedInThisIterationStep { get; private set; } + + /// + /// The total count of all recorded values in the histogram at values equal or smaller than valueIteratedTo. + /// + public long TotalCountToThisValue { get; private set; } + + /// + /// The sum of all recorded values in the histogram at values equal or smaller than valueIteratedTo. + /// + public long TotalValueToThisValue { get; private set; } + + /// + /// The percentile of recorded values in the histogram at values equal or smaller than valueIteratedTo. + /// + public double Percentile { get; private set; } + + /// + /// The percentile level that the iterator returning this HistogramIterationValue had iterated to. + /// Generally, percentileLevelIteratedTo will be equal to or smaller than percentile, but the same value point can contain multiple iteration levels for some iterators. + /// e.g. a PercentileEnumerator can stop multiple times in the exact same value point (if the count at that value covers a range of multiple percentiles in the requested percentile iteration points). + /// + public double PercentileLevelIteratedTo { get; private set; } + + /// + /// Indicates if this item is to be considered the last value in the set. + /// + /// Returns true if it is the last value, else false. + public bool IsLastValue() + { + return Math.Abs(PercentileLevelIteratedTo - 100.0D) < double.Epsilon; + } + + // Set is all-or-nothing to avoid the potential for accidental omission of some values... + internal void Set(long valueIteratedTo, + long valueIteratedFrom, + long countAtValueIteratedTo, + long countInThisIterationStep, + long totalCountToThisValue, + long totalValueToThisValue, + double percentile, + double percentileLevelIteratedTo) + { + ValueIteratedTo = valueIteratedTo; + ValueIteratedFrom = valueIteratedFrom; + CountAtValueIteratedTo = countAtValueIteratedTo; + CountAddedInThisIterationStep = countInThisIterationStep; + TotalCountToThisValue = totalCountToThisValue; + TotalValueToThisValue = totalValueToThisValue; + Percentile = percentile; + PercentileLevelIteratedTo = percentileLevelIteratedTo; + } + + /// + /// Returns a string that represents the current object. + /// + /// + /// A string that represents the current object. + /// + public override string ToString() + { + return "ValueIteratedTo:" + ValueIteratedTo + + ", ValueIteratedFrom:" + ValueIteratedFrom + + ", CountAtValueIteratedTo:" + CountAtValueIteratedTo + + ", CountAddedInThisIterationStep:" + CountAddedInThisIterationStep + + ", TotalCountToThisValue:" + TotalCountToThisValue + + ", TotalValueToThisValue:" + TotalValueToThisValue + + ", Percentile:" + Percentile + + ", PercentileLevelIteratedTo:" + PercentileLevelIteratedTo; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/PercentileEnumerable.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/PercentileEnumerable.cs new file mode 100644 index 0000000000..5169721bee --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/PercentileEnumerable.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System.Collections; +using System.Collections.Generic; + +namespace HdrHistogram.Iteration +{ + /// + /// An enumerator of through the histogram using a + /// + internal sealed class PercentileEnumerable : IEnumerable + { + private readonly HistogramBase _histogram; + private readonly int _percentileTicksPerHalfDistance; + + public PercentileEnumerable(HistogramBase histogram, int percentileTicksPerHalfDistance) + { + _histogram = histogram; + _percentileTicksPerHalfDistance = percentileTicksPerHalfDistance; + } + + public IEnumerator GetEnumerator() + { + return new PercentileEnumerator(_histogram, _percentileTicksPerHalfDistance); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/PercentileEnumerator.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/PercentileEnumerator.cs new file mode 100644 index 0000000000..b4ace5a646 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/PercentileEnumerator.cs @@ -0,0 +1,82 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; + +namespace HdrHistogram.Iteration +{ + /// + /// Used for iterating through histogram values according to percentile levels.The iteration is + /// performed in steps that start at 0% and reduce their distance to 100% according to the + /// percentileTicksPerHalfDistance parameter, ultimately reaching 100% when all recorded histogram + /// values are exhausted. + /// + internal sealed class PercentileEnumerator : AbstractHistogramEnumerator + { + private readonly int _percentileTicksPerHalfDistance; + private double _percentileLevelToIterateTo; + private bool _reachedLastRecordedValue; + + /// + /// The constructor for the + /// + /// The histogram this iterator will operate on + /// The number of iteration steps per half-distance to 100%. + public PercentileEnumerator(HistogramBase histogram, int percentileTicksPerHalfDistance) : base(histogram) + { + _percentileTicksPerHalfDistance = percentileTicksPerHalfDistance; + _percentileLevelToIterateTo = 0.0; + _reachedLastRecordedValue = false; + } + + protected override bool HasNext() + { + if (base.HasNext()) + return true; + // We want one additional last step to 100% + if (!_reachedLastRecordedValue && (ArrayTotalCount > 0)) { + _percentileLevelToIterateTo = 100.0; + _reachedLastRecordedValue = true; + return true; + } + return false; + } + + protected override void IncrementIterationLevel() + { + long percentileReportingTicks = + _percentileTicksPerHalfDistance * + (long) Math.Pow(2, + (long) (Math.Log(100.0 / (100.0 - (_percentileLevelToIterateTo))) / Math.Log(2)) + 1); + _percentileLevelToIterateTo += 100.0 / percentileReportingTicks; + } + + protected override bool ReachedIterationLevel() + { + if (CountAtThisValue == 0) + return false; + double currentPercentile = (100.0 * TotalCountToCurrentIndex) / ArrayTotalCount; + return (currentPercentile >= _percentileLevelToIterateTo); + } + + protected override double GetPercentileIteratedTo() + { + return _percentileLevelToIterateTo; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/RecordedValuesEnumerable.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/RecordedValuesEnumerable.cs new file mode 100644 index 0000000000..062f590eb0 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/RecordedValuesEnumerable.cs @@ -0,0 +1,46 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System.Collections; +using System.Collections.Generic; + +namespace HdrHistogram.Iteration +{ + /// + /// An enumerator of through the histogram using a + /// + internal sealed class RecordedValuesEnumerable : IEnumerable + { + private readonly HistogramBase _histogram; + + public RecordedValuesEnumerable(HistogramBase histogram) + { + _histogram = histogram; + } + + public IEnumerator GetEnumerator() + { + return new RecordedValuesEnumerator(_histogram); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/RecordedValuesEnumerator.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/RecordedValuesEnumerator.cs new file mode 100644 index 0000000000..1364873e6e --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Iteration/RecordedValuesEnumerator.cs @@ -0,0 +1,55 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +namespace HdrHistogram.Iteration +{ + /// + /// An enumerator that enumerate over all non-zero values. + /// + internal sealed class RecordedValuesEnumerator : AbstractHistogramEnumerator + { + private int _visitedSubBucketIndex; + private int _visitedBucketIndex; + + /// + /// The constructor for + /// + /// The histogram this iterator will operate on + public RecordedValuesEnumerator(HistogramBase histogram) :base(histogram) + { + _visitedSubBucketIndex = -1; + _visitedBucketIndex = -1; + } + + protected override void IncrementIterationLevel() + { + _visitedSubBucketIndex = CurrentSubBucketIndex; + _visitedBucketIndex = CurrentBucketIndex; + } + + protected override bool ReachedIterationLevel() + { + long currentIndexCount = SourceHistogram.GetCountAt(CurrentBucketIndex, CurrentSubBucketIndex); + return (currentIndexCount != 0) + && ( + (_visitedSubBucketIndex != CurrentSubBucketIndex) + || (_visitedBucketIndex != CurrentBucketIndex + )); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/LICENSE.txt b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/LICENSE.txt new file mode 100644 index 0000000000..850852bc86 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/LICENSE.txt @@ -0,0 +1,42 @@ +The code in this repository code was written by Lee Campbell, as a +derived work from the original Java by Gil Tene of Azul Systems and +Michael Barker, and released to the public domain, as explained +at http://creativecommons.org/publicdomain/zero/1.0/ + +For users of this code who wish to consume it under the "BSD" license +rather than under the public domain or CC0 contribution text mentioned +above, the code found under this directory is *also* provided under the +following license (commonly referred to as the BSD 2-Clause License). This +license does not detract from the above stated release of the code into +the public domain, and simply represents an additional license granted by +the Author. + +----------------------------------------------------------------------------- +** Beginning of "BSD 2-Clause License" text. ** + + Copyright (c) 2012, 2013, 2014 Gil Tene + Copyright (c) 2014 Michael Barker + Copyright (c) 2016 Lee Campbell + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/LongConcurrentHistogram.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/LongConcurrentHistogram.cs new file mode 100644 index 0000000000..5459fee049 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/LongConcurrentHistogram.cs @@ -0,0 +1,233 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Diagnostics; +using System.Threading; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// An integer values High Dynamic Range (HDR) Histogram that supports safe concurrent recording operations. + /// + /// + /// A guarantees lossless recording of values into the histogram even when the histogram is updated by multiple threads. + ///

+ /// It is important to note that concurrent recording is the only thread-safe behavior provided by . + /// It provides no implicit synchronization that would prevent the contents of the histogram from changing during other operations. + /// These non-synchronised operations include queries, iterations, copies, or addition operations on the histogram. + /// Concurrent updates that would safely work in the presence of queries, copies, or additions of histogram objects should use the which is intended for this purpose. + ///

+ ///
+ internal class LongConcurrentHistogram : HistogramBase + { + private readonly WriterReaderPhaser _wrp = new WriterReaderPhaser(); + private readonly AtomicLongArray _counts; + private long _totalCount = 0L; + + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// Providing a is useful is situations where the units used for the histogram's values are much smaller that the minimal accuracy required. + /// For example when tracking time values stated in nanoseconds, where the minimal accuracy required is a microsecond, the proper value for would be 1000. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + public LongConcurrentHistogram(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + : base(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new AtomicLongArray(CountsArrayLength); + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// An identifier for this instance. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in ticks (100 nanoseconds), where the minimal accuracy required is a + /// microsecond, the proper value for lowestTrackableValue would be 10. + /// + public LongConcurrentHistogram(long instanceId, long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + : base(instanceId, lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new AtomicLongArray(CountsArrayLength); + } + + /// + /// Gets the total number of recorded values. + /// + public override long TotalCount + { + get {return Interlocked.Read(ref _totalCount);} + protected set { Interlocked.Exchange(ref _totalCount, value); } + } + + /// + /// Returns the word size of this implementation + /// + protected override int WordSizeInBytes => sizeof(long); + + /// + /// The maximum value a count can be for any given bucket. + /// + protected override long MaxAllowableCount => long.MaxValue; + + /// + /// Copies the data from this instance to a new instance. + /// + /// A new copy of this instance. + public override HistogramBase Copy() + { + var copy = new LongConcurrentHistogram(InstanceId, LowestTrackableValue, HighestTrackableValue, NumberOfSignificantValueDigits); + copy.Add(this); + return copy; + } + + /// + /// Gets the number of recorded values at a given index. + /// + /// The index to get the count for + /// The number of recorded values at the given index. + protected override long GetCountAtIndex(int index) + { + try + { + _wrp.ReaderLock(); + + Debug.Assert(base.CountsArrayLength == _counts.Length); + + return _counts[index]; + } + finally + { + _wrp.ReaderUnlock(); + } + } + + /// + /// Sets the count at the given index. + /// + /// The index to be set + /// The value to set + protected override void SetCountAtIndex(int index, long value) + { + //At time of writing, this was unused. + // This method is only used in decoding from byte array, in which case the basic histogram implementations would be used. + // You can then Add these decoded instances to this Concurrent implementation. + + //Throwing as this code is currently untested. + throw new NotImplementedException(); + //try + //{ + // _wrp.ReaderLock(); + // Debug.Assert(CountsArrayLength == _activeCounts.Length); + // Debug.Assert(CountsArrayLength == _inactiveCounts.Length); + // var idx = NormalizeIndex(index, _activeCounts.NormalizingIndexOffset, _activeCounts.Length); + // _activeCounts.LazySet(idx, value); + // idx = NormalizeIndex(index, _inactiveCounts.NormalizingIndexOffset, _inactiveCounts.Length); + // _inactiveCounts.LazySet(idx, 0); + //} + //finally + //{ + // _wrp.ReaderUnlock(); + //} + } + + /// + /// Increments the count at the given index. Will also increment the . + /// + /// The index to increment the count at. + protected override void IncrementCountAtIndex(int index) + { + long criticalValue = _wrp.WriterCriticalSectionEnter(); + try + { + _counts.IncrementAndGet(index); + Interlocked.Increment(ref _totalCount); + } + finally + { + _wrp.WriterCriticalSectionExit(criticalValue); + } + } + + /// + /// Adds the specified amount to the count of the provided index. Also increments the by the same amount. + /// + /// The index to increment. + /// The amount to increment by. + protected override void AddToCountAtIndex(int index, long addend) + { + long criticalValue = _wrp.WriterCriticalSectionEnter(); + try + { + _counts.AddAndGet(index, addend); + Interlocked.Add(ref _totalCount, addend); + } + finally + { + _wrp.WriterCriticalSectionExit(criticalValue); + } + } + + /// + /// Clears the counts of this implementation. + /// + protected override void ClearCounts() + { + try + { + _wrp.ReaderLock(); + Debug.Assert(CountsArrayLength == _counts.Length); + for (int i = 0; i<_counts.Length; i++) + { + _counts[i] = 0; + } + TotalCount = 0; + } + finally + { + _wrp.ReaderUnlock(); + } + } + + /// + /// Copies the internal counts array into the supplied array. + /// + /// The array to write each count value into. + protected override void CopyCountsInto(long[] target) + { + for (int i = 0; i + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; + +namespace HdrHistogram +{ + /// + /// A High Dynamic Range (HDR) Histogram + /// + /// + /// Histogram supports the recording and analyzing sampled data value counts across a configurable integer value + /// range with configurable value precision within the range. + /// Value precision is expressed as the number of significant digits in the value recording, and provides control + /// over value quantization behavior across the value range and the subsequent value resolution at any given level. + /// + /// For example, a Histogram could be configured to track the counts of observed integer values between 0 and + /// 36,000,000,000 while maintaining a value precision of 3 significant digits across that range. + /// Value quantization within the range will thus be no larger than 1/1,000th (or 0.1%) of any value. + /// This example Histogram could be used to track and analyze the counts of observed response times ranging between + /// 100 nanoseconds and 1 hour in magnitude, while maintaining a value resolution of 100 nanosecond up to + /// 100 microseconds, a resolution of 1 millisecond(or better) up to one second, and a resolution of 1 second + /// (or better) up to 1,000 seconds. + /// At it's maximum tracked value(1 hour), it would still maintain a resolution of 3.6 seconds (or better). + /// + /// Histogram tracks value counts in fields. + /// + internal class LongHistogram : HistogramBase + { + private readonly long[] _counts; + private long _totalCount; + + /// + /// Construct a Histogram given the highest value to be tracked and a number of significant decimal digits. + /// The histogram will be constructed to implicitly track(distinguish from 0) values as low as 1. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= 2. + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + public LongHistogram(long highestTrackableValue, int numberOfSignificantValueDigits) + : this(1, highestTrackableValue, numberOfSignificantValueDigits) + { + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// Providing a is useful is situations where the units used for the histogram's values are much smaller that the minimal accuracy required. + /// For example when tracking time values stated in nanosecond units, where the minimal accuracy required is a microsecond, the proper value for would be 1000. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + public LongHistogram(long lowestTrackableValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + : base(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new long[CountsArrayLength]; + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// An identifier for this instance. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in ticks (100 nanoseconds), where the minimal accuracy required is a + /// microsecond, the proper value for lowestTrackableValue would be 10. + /// + public LongHistogram(long instanceId, long lowestTrackableValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + : base(instanceId, lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new long[CountsArrayLength]; + } + + + /// + /// Gets the total number of recorded values. + /// + public override long TotalCount { get { return _totalCount; } protected set { _totalCount = value; } } + + /// + /// Returns the word size of this implementation + /// + protected override int WordSizeInBytes => 8; + + /// + /// The maximum value a count can be for any given bucket. + /// + protected override long MaxAllowableCount => long.MaxValue; + + /// + /// Create a copy of this histogram, complete with data and everything. + /// + /// A distinct copy of this histogram. + public override HistogramBase Copy() + { + var copy = new LongHistogram(LowestTrackableValue, HighestTrackableValue, NumberOfSignificantValueDigits); + copy.Add(this); + return copy; + } + + /// + /// Gets the number of recorded values at a given index. + /// + /// The index to get the count for + /// The number of recorded values at the given index. + protected override long GetCountAtIndex(int index) + { + return _counts[index]; + } + + /// + /// Sets the count at the given index. + /// + /// The index to be set + /// The value to set + protected override void SetCountAtIndex(int index, long value) + { + _counts[index] = value; + } + + /// + /// Increments the count at the given index. Will also increment the . + /// + /// The index to increment the count at. + protected override void IncrementCountAtIndex(int index) + { + _counts[index]++; + _totalCount++; + } + + /// + /// Adds the specified amount to the count of the provided index. Also increments the by the same amount. + /// + /// The index to increment. + /// The amount to increment by. + protected override void AddToCountAtIndex(int index, long addend) + { + _counts[index] += addend; + _totalCount += addend; + } + + /// + /// Clears the counts of this implementation. + /// + protected override void ClearCounts() + { + Array.Clear(_counts, 0, _counts.Length); + _totalCount = 0; + } + + /// + /// Copies the internal counts array into the supplied array. + /// + /// The array to write each count value into. + protected override void CopyCountsInto(long[] target) + { + Array.Copy(_counts, target, target.Length); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/CsvOutputFormatter.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/CsvOutputFormatter.cs new file mode 100644 index 0000000000..aaed54ea37 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/CsvOutputFormatter.cs @@ -0,0 +1,57 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.IO; +using HdrHistogram.Iteration; + +namespace HdrHistogram.Output +{ + internal sealed class CsvOutputFormatter : IOutputFormatter + { + private readonly string _percentileFormatString; + private readonly string _lastLinePercentileFormatString; + private readonly TextWriter _textWriter; + private readonly double _outputValueUnitScalingRatio; + + public CsvOutputFormatter(TextWriter textWriter, int significantDigits, double outputValueUnitScalingRatio) + { + _textWriter = textWriter; + _outputValueUnitScalingRatio = outputValueUnitScalingRatio; + _percentileFormatString = "{0:F" + significantDigits + "},{1:F12},{2},{3:F2}\n"; + _lastLinePercentileFormatString = "{0:F" + significantDigits + "},{1:F12},{2},Infinity\n"; + } + + public void WriteHeader() + { + _textWriter.Write("\"Value\",\"Percentile\",\"TotalCount\",\"1/(1-Percentile)\"\n"); + } + + public void WriteValue(HistogramIterationValue iterationValue) + { + var scaledValue = iterationValue.ValueIteratedTo / _outputValueUnitScalingRatio; + var percentile = iterationValue.PercentileLevelIteratedTo / 100.0D; + + if (iterationValue.IsLastValue()) + { + _textWriter.Write(_lastLinePercentileFormatString, scaledValue, percentile, iterationValue.TotalCountToThisValue); + } + else + { + _textWriter.Write(_percentileFormatString, scaledValue, percentile, iterationValue.TotalCountToThisValue, 1 / (1.0D - percentile)); + + } + } + + public void WriteFooter(HistogramBase histogram) + { + //No op + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/HgrmOutputFormatter.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/HgrmOutputFormatter.cs new file mode 100644 index 0000000000..a015cf5a73 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/HgrmOutputFormatter.cs @@ -0,0 +1,79 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.IO; +using HdrHistogram.Iteration; + +namespace HdrHistogram.Output +{ + + internal sealed class HgrmOutputFormatter : IOutputFormatter + { + private readonly TextWriter _printStream; + private readonly double _outputValueUnitScalingRatio; + private readonly string _percentileFormatString; + private readonly string _lastLinePercentileFormatString; + private readonly string _footerLine1FormatString; + private readonly string _footerLine2FormatString; + private readonly string _footerLine3FormatString; + + public HgrmOutputFormatter(TextWriter printStream, int significantDigits, double outputValueUnitScalingRatio) + { + _printStream = printStream; + _outputValueUnitScalingRatio = outputValueUnitScalingRatio; + _percentileFormatString = "{0,12:F" + significantDigits + "} {1,2:F12} {2,10} {3,14:F2}\n"; + _lastLinePercentileFormatString = "{0,12:F" + significantDigits + "} {1,2:F12} {2,10}\n"; + _footerLine1FormatString = "#[Mean = {0,12:F" + significantDigits + "}, StdDeviation = {1,12:F" + significantDigits + "}]\n"; + _footerLine2FormatString = "#[Max = {0,12:F" + significantDigits + "}, Total count = {1,12}]\n"; + _footerLine3FormatString = "#[Buckets = {0,12}, SubBuckets = {1,12}]\n"; + } + + public void WriteHeader() + { + _printStream.Write("{0,12} {1,14} {2,10} {3,14}\n\n", "Value", "Percentile", "TotalCount", "1/(1-Percentile)"); + } + + public void WriteValue(HistogramIterationValue iterationValue) + { + var scaledValue = iterationValue.ValueIteratedTo / _outputValueUnitScalingRatio; + var percentile = iterationValue.PercentileLevelIteratedTo / 100.0D; + + if (iterationValue.IsLastValue()) + { + _printStream.Write(_lastLinePercentileFormatString, scaledValue, percentile, iterationValue.TotalCountToThisValue); + } + else + { + _printStream.Write(_percentileFormatString, scaledValue, percentile, iterationValue.TotalCountToThisValue, 1 / (1.0D - percentile)); + + } + } + + public void WriteFooter(HistogramBase histogram) + { + // Calculate and output mean and std. deviation. + // Note: mean/std. deviation numbers are very often completely irrelevant when + // data is extremely non-normal in distribution (e.g. in cases of strong multi-modal + // response time distribution associated with GC pauses). However, reporting these numbers + // can be very useful for contrasting with the detailed percentile distribution + // reported by outputPercentileDistribution(). It is not at all surprising to find + // percentile distributions where results fall many tens or even hundreds of standard + // deviations away from the mean - such results simply indicate that the data sampled + // exhibits a very non-normal distribution, highlighting situations for which the std. + // deviation metric is a useless indicator. + + var mean = histogram.GetMean() / _outputValueUnitScalingRatio; + var stdDeviation = histogram.GetStdDeviation() / _outputValueUnitScalingRatio; + _printStream.Write(_footerLine1FormatString, mean, stdDeviation); + _printStream.Write(_footerLine2FormatString, histogram.GetMaxValue() / _outputValueUnitScalingRatio, histogram.TotalCount); + _printStream.Write(_footerLine3FormatString, histogram.BucketCount, histogram.SubBucketCount); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/IOutputFormatter.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/IOutputFormatter.cs new file mode 100644 index 0000000000..2fe034e465 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Output/IOutputFormatter.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Iteration; + +namespace HdrHistogram.Output +{ + internal interface IOutputFormatter + { + void WriteHeader(); + void WriteValue(HistogramIterationValue value); + void WriteFooter(HistogramBase histogram); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/OutputScalingFactor.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/OutputScalingFactor.cs new file mode 100644 index 0000000000..7bb89f4e0e --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/OutputScalingFactor.cs @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System.Diagnostics; + +namespace HdrHistogram +{ + /// + /// Provides constants to use in selecting a scaling factor for output of a histograms recordings. + /// + internal static class OutputScalingFactor + { + /// + /// For use when values are recorded and reported in the same unit of measurement. + /// + public const double None = 1.0; + + /// + /// For use when values are recorded with and output should be reported in microseconds. + /// + public static readonly double TimeStampToMicroseconds = Stopwatch.Frequency / (1000d * 1000d); + + /// + /// For use when values are recorded with and output should be reported in milliseconds. + /// + public static readonly double TimeStampToMilliseconds = Stopwatch.Frequency / 1000d; + + /// + /// For use when values are recorded with and output should be reported in seconds. + /// + public static readonly double TimeStampToSeconds = Stopwatch.Frequency; + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/CountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/CountsDecoder.cs new file mode 100644 index 0000000000..af1ef81254 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/CountsDecoder.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; +using System.Linq; + +namespace HdrHistogram.Persistence +{ + /// + /// Provides a method to get the correct implementation for a given word size. + /// + internal static class CountsDecoder + { + private static readonly IDictionary Decoders; + + static CountsDecoder() + { + Decoders = new ICountsDecoder[] + { + new ShortCountsDecoder(), + new IntCountsDecoder(), + new LongCountsDecoder(), + new V2MaxWordSizeCountsDecoder(), + }.ToDictionary(cd => cd.WordSize); + } + + /// + /// Gets the correct implementation of a for the supplied word size. + /// + /// The word size of the encoded histogram + /// A implementation. + public static ICountsDecoder GetDecoderForWordSize(int wordSize) + { + return Decoders[wordSize]; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/ICountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/ICountsDecoder.cs new file mode 100644 index 0000000000..6507df0c07 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/ICountsDecoder.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using HdrHistogram.Utilities; + +namespace HdrHistogram.Persistence +{ + /// + /// Defines methods to read counts values from a potentially encoded . + /// + internal interface ICountsDecoder + { + /// + /// The target word size for the encoder. + /// + int WordSize { get; } + + /// + /// Decodes from a supplied count values and calls a delegate with index and count. + /// + /// The source of the data. + /// The length in bytes to read. + /// + /// A delegate to call with the count for a given index. + /// The index that was read to. + int ReadCounts(ByteBuffer sourceBuffer, int lengthInBytes, int maxIndex, Action setCount); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/IntCountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/IntCountsDecoder.cs new file mode 100644 index 0000000000..09f4c478de --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/IntCountsDecoder.cs @@ -0,0 +1,24 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram.Persistence +{ + sealed class IntCountsDecoder : SimpleCountsDecoder + { + public override int WordSize => 4; + + protected override long ReadValue(ByteBuffer sourceBuffer) + { + return sourceBuffer.GetInt(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/LongCountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/LongCountsDecoder.cs new file mode 100644 index 0000000000..eafa22d76d --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/LongCountsDecoder.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram.Persistence +{ + sealed class LongCountsDecoder : SimpleCountsDecoder + { + public override int WordSize => 8; + protected override long ReadValue(ByteBuffer sourceBuffer) + { + return sourceBuffer.GetLong(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/ShortCountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/ShortCountsDecoder.cs new file mode 100644 index 0000000000..67a04f31b6 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/ShortCountsDecoder.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram.Persistence +{ + sealed class ShortCountsDecoder : SimpleCountsDecoder + { + public override int WordSize => 2; + protected override long ReadValue(ByteBuffer sourceBuffer) + { + return sourceBuffer.GetShort(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/SimpleCountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/SimpleCountsDecoder.cs new file mode 100644 index 0000000000..cb98143f97 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/SimpleCountsDecoder.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using HdrHistogram.Utilities; + +namespace HdrHistogram.Persistence +{ + abstract class SimpleCountsDecoder : ICountsDecoder + { + public abstract int WordSize { get; } + + public int ReadCounts(ByteBuffer sourceBuffer, int lengthInBytes, int maxIndex, Action setCount) + { + var idx = 0; + int endPosition = sourceBuffer.Position + lengthInBytes; + while (sourceBuffer.Position < endPosition) + { + var item = ReadValue(sourceBuffer); + setCount(idx++, item); + } + return idx; + } + + protected abstract long ReadValue(ByteBuffer sourceBuffer); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/V2MaxWordSizeCountsDecoder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/V2MaxWordSizeCountsDecoder.cs new file mode 100644 index 0000000000..e06ef1dac5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Persistence/V2MaxWordSizeCountsDecoder.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using HdrHistogram.Utilities; + +namespace HdrHistogram.Persistence +{ + sealed class V2MaxWordSizeCountsDecoder : ICountsDecoder + { + // LEB128-64b9B + ZigZag require up to 9 bytes per word + public int WordSize => 9; + + public int ReadCounts(ByteBuffer sourceBuffer, int lengthInBytes, int maxIndex, Action setCount) + { + var idx = 0; + int endPosition = sourceBuffer.Position + lengthInBytes; + while (sourceBuffer.Position < endPosition && idx < maxIndex) + { + var item = ZigZagEncoding.GetLong(sourceBuffer); + if (item < 0) + { + var zeroCounts = -(item); + if (zeroCounts > int.MaxValue) + { + throw new ArgumentException("An encoded zero count of > int.MaxValue was encountered in the source"); + } + idx += (int)zeroCounts; + } + else + { + setCount(idx++, item); + } + } + return idx; + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Recorder.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Recorder.cs new file mode 100644 index 0000000000..96fd44596a --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Recorder.cs @@ -0,0 +1,249 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; +using System.Threading; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// Records integer values, and provides stable interval samples from live recorded data without interrupting or stalling active recording of values. + /// Each interval histogram provided contains all value counts accumulated since the previous interval histogram was taken. + /// + /// + /// This pattern is commonly used in logging interval histogram information while recording is ongoing. + /// Recording calls are wait-free on architectures that support atomic increment operations, and are lock-free on architectures that do not. + /// + internal class Recorder : IRecorder + { + private static long _instanceIdSequencer = 1; + + private readonly object _gate = new object(); + private readonly long _instanceId = Interlocked.Increment(ref _instanceIdSequencer); + private readonly WriterReaderPhaser _recordingPhaser = new WriterReaderPhaser(); + private readonly HistogramFactoryDelegate _histogramFactory; + + private HistogramBase _activeHistogram; + private HistogramBase _inactiveHistogram; + + /// + /// Creates a recorder that will delegate recording to histograms created from these parameters. + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// The factory to be used to actually create instances of . + public Recorder( + long lowestDiscernibleValue, + long highestTrackableValue, + int numberOfSignificantValueDigits, + HistogramFactoryDelegate histogramFactory) + { + _histogramFactory = histogramFactory; + _activeHistogram = histogramFactory(_instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + _inactiveHistogram = histogramFactory(_instanceId, lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits); + _activeHistogram.StartTimeStamp = DateTime.Now.MillisecondsSinceUnixEpoch(); + } + + /// + /// Records a value in the histogram + /// + /// The value to be recorded + /// if value is exceeds highestTrackableValue + public void RecordValue(long value) + { + var criticalValueAtEnter = _recordingPhaser.WriterCriticalSectionEnter(); + try + { + _activeHistogram.RecordValue(value); + } + finally + { + _recordingPhaser.WriterCriticalSectionExit(criticalValueAtEnter); + } + } + + /// + /// Record a value in the histogram (adding to the value's current count) + /// + /// The value to be recorded + /// The number of occurrences of this value to record + /// if value is exceeds highestTrackableValue + public void RecordValueWithCount(long value, long count) + { + var criticalValueAtEnter = _recordingPhaser.WriterCriticalSectionEnter(); + try + { + _activeHistogram.RecordValueWithCount(value, count); + } + finally + { + _recordingPhaser.WriterCriticalSectionExit(criticalValueAtEnter); + } + } + + /// + /// Record a value in the histogram. + /// + /// The value to record + /// If is larger than 0, add auto-generated value records as appropriate if is larger than + /// if value is exceeds highestTrackableValue + /// + /// To compensate for the loss of sampled values when a recorded value is larger than the expected interval between value samples, + /// Histogram will auto-generate an additional series of decreasingly-smaller (down to the expectedIntervalBetweenValueSamples) value records. + /// + /// Note: This is a at-recording correction method, as opposed to the post-recording correction method provided by currently unimplemented CopyCorrectedForCoordinatedOmission method. + /// The two methods are mutually exclusive, and only one of the two should be be used on a given data set to correct for the same coordinated omission issue. + /// + /// See notes in the description of the Histogram calls for an illustration of why this corrective behavior is important. + /// + public void RecordValueWithExpectedInterval(long value, long expectedIntervalBetweenValueSamples) + { + var criticalValueAtEnter = _recordingPhaser.WriterCriticalSectionEnter(); + try + { + _activeHistogram.RecordValueWithExpectedInterval(value, expectedIntervalBetweenValueSamples); + } + finally + { + _recordingPhaser.WriterCriticalSectionExit(criticalValueAtEnter); + } + } + + /// + /// Get a new instance of an interval histogram, which will include a stable, consistent view of all value counts accumulated since the last interval histogram was taken. + /// Calling will reset the value counts, and start accumulating value counts for the next interval. + /// + /// A histogram containing the value counts accumulated since the last interval histogram was taken. + public HistogramBase GetIntervalHistogram() + { + return GetIntervalHistogram(null); + } + + /// + /// Get a new instance of an interval histogram, which will include a stable, consistent view of all value counts accumulated since the last interval histogram was taken. + /// Calling will reset the value counts, and start accumulating value counts for the next interval. + /// + /// a previously returned interval histogram that may be recycled to avoid allocation and copy operations. + /// A histogram containing the value counts accumulated since the last interval histogram was taken. + /// + /// accepts a previously returned interval histogram that can be recycled internally to avoid allocation and content copying operations. + /// It is therefore significantly more efficient for repeated use than and . + /// The provided must be either be null or an interval histogram returned by a previous call to or . + /// NOTE: The caller is responsible for not recycling the same returned interval histogram more than once. + /// If the same interval histogram instance is recycled more than once, behavior is undefined. + /// + public HistogramBase GetIntervalHistogram(HistogramBase histogramToRecycle) + { + lock (_gate) + { + // Verify that replacement histogram can validly be used as an inactive histogram replacement: + ValidateFitAsReplacementHistogram(histogramToRecycle); + _inactiveHistogram = histogramToRecycle; + PerformIntervalSample(); + var sampledHistogram = _inactiveHistogram; + _inactiveHistogram = null; // Once we expose the sample, we can't reuse it internally until it is recycled + return sampledHistogram; + } + } + + /// + /// Place a copy of the value counts accumulated since accumulated (since the last interval histogram was taken) into . + /// This will overwrite the existing data in . + /// Calling will reset the value counts, and start accumulating value counts for the next interval. + /// + /// The histogram into which the interval histogram's data should be copied. + public void GetIntervalHistogramInto(HistogramBase targetHistogram) + { + lock (_gate) + { + PerformIntervalSample(); + _inactiveHistogram.CopyInto(targetHistogram); + } + } + + /// + /// Reset any value counts accumulated thus far. + /// + public void Reset() + { + lock (_gate) + { + // the currently inactive histogram is reset each time we flip. So flipping twice resets both: + PerformIntervalSample(); + PerformIntervalSample(); + } + } + + private void PerformIntervalSample() + { + try + { + _recordingPhaser.ReaderLock(); + + // Make sure we have an inactive version to flip in: + if (_inactiveHistogram == null) + { + _inactiveHistogram = _histogramFactory(_instanceId, + _activeHistogram.LowestTrackableValue, + _activeHistogram.HighestTrackableValue, + _activeHistogram.NumberOfSignificantValueDigits); + } + + _inactiveHistogram.Reset(); + + // Swap active and inactive histograms: + var tempHistogram = _inactiveHistogram; + _inactiveHistogram = _activeHistogram; + _activeHistogram = tempHistogram; + + // Mark end time of previous interval and start time of new one: + var now = DateTime.Now.MillisecondsSinceUnixEpoch(); + _activeHistogram.StartTimeStamp = now; + _inactiveHistogram.EndTimeStamp = now; + + // Make sure we are not in the middle of recording a value on the previously active histogram: + + // Flip phase to make sure no recordings that were in flight pre-flip are still active: + _recordingPhaser.FlipPhase(TimeSpan.FromMilliseconds(0.5)); + } + finally + { + _recordingPhaser.ReaderUnlock(); + } + } + + private void ValidateFitAsReplacementHistogram(HistogramBase replacementHistogram) + { + if(replacementHistogram !=null && replacementHistogram.InstanceId != _activeHistogram.InstanceId) + { + throw new InvalidOperationException( + $"Replacement histogram must have been obtained via a previous getIntervalHistogram() call from this {GetType().Name} instance"); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/ShortHistogram.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/ShortHistogram.cs new file mode 100644 index 0000000000..e4a731b456 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/ShortHistogram.cs @@ -0,0 +1,198 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// A High Dynamic Range (HDR) Histogram using a count type + /// + /// + /// Histogram supports the recording and analyzing sampled data value counts across a configurable integer value + /// range with configurable value precision within the range. + /// Value precision is expressed as the number of significant digits in the value recording, and provides control + /// over value quantization behavior across the value range and the subsequent value resolution at any given level. + /// + /// For example, a Histogram could be configured to track the counts of observed integer values between 0 and + /// 36,000,000,000 while maintaining a value precision of 3 significant digits across that range. + /// Value quantization within the range will thus be no larger than 1/1,000th (or 0.1%) of any value. + /// This example Histogram could be used to track and analyze the counts of observed response times ranging between + /// 100 nanoseconds and 1 hour in magnitude, while maintaining a value resolution of 100 nanosecond up to + /// 100 microseconds, a resolution of 1 millisecond(or better) up to one second, and a resolution of 1 second + /// (or better) up to 1,000 seconds. + /// At it's maximum tracked value(1 hour), it would still maintain a resolution of 3.6 seconds (or better). + /// + /// Histogram tracks value counts in fields. + /// Other field types are available in the and + /// implementations of . + /// + internal sealed class ShortHistogram : HistogramBase + { + private readonly short[] _counts; + + private long _totalCount; + + /// + /// Construct a given the highest value to be tracked and a number of significant decimal digits. + /// The histogram will be constructed to implicitly track (distinguish from 0) values as low as 1. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= 2. + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation.Must be a non-negative integer between 0 and 5. + public ShortHistogram(long highestTrackableValue, int numberOfSignificantValueDigits) + : this(1, highestTrackableValue, numberOfSignificantValueDigits) + { + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// Providing a is useful is situations where the units used for the histogram's values are much smaller that the minimal accuracy required. + /// For example when tracking time values stated in nanoseconds, where the minimal accuracy required is a microsecond, the proper value for would be 1000. + /// + /// + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. Must be a positive integer that is >= (2 * ). + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + public ShortHistogram(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + : base(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new short[CountsArrayLength]; + } + + /// + /// Construct a given the lowest and highest values to be tracked and a number of significant decimal digits. + /// + /// An identifier for this instance. + /// The lowest value that can be tracked (distinguished from 0) by the histogram. + /// Must be a positive integer that is >= 1. + /// May be internally rounded down to nearest power of 2. + /// + /// The highest value to be tracked by the histogram. + /// Must be a positive integer that is >= (2 * lowestTrackableValue). + /// + /// + /// The number of significant decimal digits to which the histogram will maintain value resolution and separation. + /// Must be a non-negative integer between 0 and 5. + /// + /// + /// Providing a lowestTrackableValue is useful in situations where the units used for the histogram's values are much + /// smaller that the minimal accuracy required. + /// For example when tracking time values stated in ticks (100 nanoseconds), where the minimal accuracy required is a + /// microsecond, the proper value for lowestTrackableValue would be 10. + /// + public ShortHistogram(long instanceId, long lowestTrackableValue, long highestTrackableValue, + int numberOfSignificantValueDigits) + : base(instanceId, lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits) + { + _counts = new short[CountsArrayLength]; + } + + /// + /// Gets the total number of recorded values. + /// + public override long TotalCount { get { return _totalCount; } protected set { _totalCount = value; } } + + /// + /// Returns the word size of this implementation + /// + protected override int WordSizeInBytes => 2; + + /// + /// The maximum value a count can be for any given bucket. + /// + protected override long MaxAllowableCount => short.MaxValue; + + /// + /// Create a copy of this histogram, complete with data and everything. + /// + /// A distinct copy of this histogram. + public override HistogramBase Copy() + { + var copy = new ShortHistogram(LowestTrackableValue, HighestTrackableValue, NumberOfSignificantValueDigits); + copy.Add(this); + return copy; + } + + /// + /// Gets the number of recorded values at a given index. + /// + /// The index to get the count for + /// The number of recorded values at the given index. + protected override long GetCountAtIndex(int index) + { + return _counts[index]; + } + + /// + /// Sets the count at the given index. + /// + /// The index to be set + /// The value to set + protected override void SetCountAtIndex(int index, long value) + { + _counts[index] = (short)value; + } + + /// + /// Increments the count at the given index. Will also increment the . + /// + /// The index to increment the count at. + protected override void IncrementCountAtIndex(int index) + { + _counts[index]++; + _totalCount++; + } + + /// + /// Adds the specified amount to the count of the provided index. Also increments the by the same amount. + /// + /// The index to increment. + /// The amount to increment by. + protected override void AddToCountAtIndex(int index, long addend) + { + _counts[index] += (short)addend; + _totalCount += addend; + } + + /// + /// Clears the counts of this implementation. + /// + protected override void ClearCounts() + { + Array.Clear(_counts, 0, _counts.Length); + _totalCount = 0; + } + + /// + /// Copies the internal counts array into the supplied array. + /// + /// The array to write each count value into. + protected override void CopyCountsInto(long[] target) + { + for (int i = 0; i < target.Length; i++) + { + target[i] = _counts[i]; + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/TimeStamp.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/TimeStamp.cs new file mode 100644 index 0000000000..dc16b93d15 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/TimeStamp.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Diagnostics; + +namespace HdrHistogram +{ + /// + /// Helper methods to get time periods based in system stopwatch units. + /// + internal static class TimeStamp + { + /// + /// Return a representing the number system timer ticks that occur over the provided number of seconds. + /// + /// A number seconds to represent. + /// The number of system timer ticks that represent the . + public static long Seconds(long seconds) + { + return Stopwatch.Frequency * seconds; + } + + /// + /// Return a representing the number system timer ticks that occur over the provided number of minutes. + /// + /// A number minutes to represent. + /// The number of system timer ticks that represent the . + public static long Minutes(long minutes) + { + return minutes * Seconds(60); + } + + /// + /// Return a representing the number system timer ticks that occur over the provided number of hours. + /// + /// A number hours to represent. + /// The number of system timer ticks that represent the . + public static long Hours(int hours) + { + return hours * Minutes(60); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ArrayExtensions.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ArrayExtensions.cs new file mode 100644 index 0000000000..20e4d1b4a4 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ArrayExtensions.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +namespace HdrHistogram.Utilities +{ + /// + /// Extension methods for Arrays. + /// + internal static class ArrayExtensions + { + /// + /// Checks if the two arrays have the same items in the same order. + /// + /// The type of the items in the arrays. + /// The source array to check. + /// The other array to check against. + /// Returns true if the arrays are of the same length and each item by index is equal, else false. + public static bool IsSequenceEqual(this T[] source, T[] other) + { + if (source.Length != other.Length) + return false; + + for (int i = 0; i < other.Length; i++) + { + if (!Equals(source[i], other[i])) + { + return false; + } + } + return true; + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/AtomicIntArray.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/AtomicIntArray.cs new file mode 100644 index 0000000000..00646e73c5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/AtomicIntArray.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Threading; + +namespace HdrHistogram.Utilities +{ + /// + /// This is a basic implementation/port, of just the methods that are required internally. + /// + internal sealed class AtomicIntArray + { + private readonly int[] _counts; + + public AtomicIntArray(int arrayLength) + { + _counts = new int[arrayLength]; + } + + public int Length => _counts.Length; + + public int this[int index] + { + get { return _counts[index]; } + set + { + LazySet(index, value); + } + } + + public int IncrementAndGet(int index) + { + return Interlocked.Add(ref _counts[index], 1); + } + + public int AddAndGet(int index, int value) + { + return Interlocked.Add(ref _counts[index], value); + } + + private void LazySet(int index, int value) + { + // TODO Revisit this, work out which method is the same as lazySet!!! + // Note this is only called when we are clearing out the AtomicHistogram (From AtomicHistogram clearCounts()), + // so it's not performance critical (i.e. not on Hot-Path), but still worth looking at + + // From http://stackoverflow.com/questions/8381440/how-is-lazyset-in-javas-atomic-classes-implemented/8420284#8420284 + // For people who like to think of these operations in terms of machine-level barriers on common multiprocessors, lazySet provides a + // preceding store-store barrier (which is either a no-op or very cheap on current platforms), but no store-load barrier (which is + // usually the expensive part of a volatile-write) + + // From http://mechanitis.blogspot.co.uk/2011/10/mike-and-i-debut-our-new-disruptor.html?showComment=1320601640614&_sm_au_=iVVrQRjv5k6LJ0k5#c4014181423217401642 + // The main difference between lazySet and a volatile write is that the lazySet does not guarantee that the value is made immediately visible, + // i.e. store buffers are not immediate flushed out to memory. The value will still become visible, eventually. + // The guarantee that the lazySet provides is that the data will be made visible in the correct order. I.e. the write to the ring buffer will + // occur before the update of the associated sequence. + + // http://mechanical-sympathy.blogspot.co.uk/2011/08/disruptor-20-released.html?showComment=1322219765503&_sm_au_=iVVrQRjv5k6LJ0k5#c8612572394778861289 + // The JVM/JIT is able to optimise and in the case of x86 the AtomicLong.lazySet() is simply a software, rather than hardware, memory barrier. + // On other platforms it may need a hardware memory barrier/fence, e.g. ARM. + + // From http://mentablog.soliveirajr.com/2012/12/asynchronous-logging-versus-memory-mapped-files/ + // The Java atomic variables (AtomicLong, AtomicInteger, etc.) have get() and set() methods that work as reads and writes on volatile variables with + // the additional feature of a semi-volatile write. When you change the value of a atomic variable through its lazySet() method, the value is written + // to the local cache of the core but not immediately to main memory. As a result, it will take an indefinite amount of time for the change to be + // flushed out to main memory so that other threads can see the new value. + + _counts[index] = value; + // Is this right, is that all we need??? We definitely don't want an Interlocked here, that's too much!! + //Thread.MemoryBarrier(); + Interlocked.MemoryBarrier(); + + //Volatile.Read (only emits half-fence (acquire fence) as opposed to Thread.VolatileRead which emits a full-fence + + // From http://msdn.microsoft.com/en-us/magazine/jj863136.aspx + // An operation that’s closely related to Interlocked methods is Thread.MemoryBarrier, which can be thought of as a dummy Interlocked operation. + // Just like an Interlocked method, Thread.Memory­Barrier can’t be reordered with any prior or subsequent memory operations. Unlike an Interlocked + // method, though, Thread.MemoryBarrier has no side effect; it simply constrains memory reorderings. + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/AtomicLongArray.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/AtomicLongArray.cs new file mode 100644 index 0000000000..028b1f72cc --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/AtomicLongArray.cs @@ -0,0 +1,97 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * Written by Matt Warren, and released to the public domain, + * as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + */ +using System.Threading; + +namespace HdrHistogram.Utilities +{ + /// + /// This is a basic implementation/port, of just the methods that are required internally. + /// + internal sealed class AtomicLongArray + { + private readonly long[] _counts; + + public AtomicLongArray(int arrayLength) + { + _counts = new long[arrayLength]; + } + + public int Length => _counts.Length; + + public long this[int index] + { + get { return Interlocked.Read(ref _counts[index]); } + set + { + LazySet(index, value); + } + } + + public long IncrementAndGet(int index) + { + return Interlocked.Add(ref _counts[index], 1); + } + + public long AddAndGet(int index, long value) + { + return Interlocked.Add(ref _counts[index], value); + } + + private void LazySet(int index, long value) + { + // TODO Revisit this, work out which method is the same as lazySet!!! + // Note this is only called when we are clearing out the AtomicHistogram (From AtomicHistogram clearCounts()), + // so it's not performance critical (i.e. not on Hot-Path), but still worth looking at + + // From http://stackoverflow.com/questions/8381440/how-is-lazyset-in-javas-atomic-classes-implemented/8420284#8420284 + // For people who like to think of these operations in terms of machine-level barriers on common multiprocessors, lazySet provides a + // preceding store-store barrier (which is either a no-op or very cheap on current platforms), but no store-load barrier (which is + // usually the expensive part of a volatile-write) + + // From http://mechanitis.blogspot.co.uk/2011/10/mike-and-i-debut-our-new-disruptor.html?showComment=1320601640614&_sm_au_=iVVrQRjv5k6LJ0k5#c4014181423217401642 + // The main difference between lazySet and a volatile write is that the lazySet does not guarantee that the value is made immediately visible, + // i.e. store buffers are not immediate flushed out to memory. The value will still become visible, eventually. + // The guarantee that the lazySet provides is that the data will be made visible in the correct order. I.e. the write to the ring buffer will + // occur before the update of the associated sequence. + + // http://mechanical-sympathy.blogspot.co.uk/2011/08/disruptor-20-released.html?showComment=1322219765503&_sm_au_=iVVrQRjv5k6LJ0k5#c8612572394778861289 + // The JVM/JIT is able to optimise and in the case of x86 the AtomicLong.lazySet() is simply a software, rather than hardware, memory barrier. + // On other platforms it may need a hardware memory barrier/fence, e.g. ARM. + + // From http://mentablog.soliveirajr.com/2012/12/asynchronous-logging-versus-memory-mapped-files/ + // The Java atomic variables (AtomicLong, AtomicInteger, etc.) have get() and set() methods that work as reads and writes on volatile variables with + // the additional feature of a semi-volatile write. When you change the value of a atomic variable through its lazySet() method, the value is written + // to the local cache of the core but not immediately to main memory. As a result, it will take an indefinite amount of time for the change to be + // flushed out to main memory so that other threads can see the new value. + + _counts[index] = value; + // Is this right, is that all we need??? We definitely don't want an Interlocked here, that's too much!! + //Thread.MemoryBarrier(); + Interlocked.MemoryBarrier(); + + //Volatile.Read (only emits half-fence (acquire fence) as opposed to Thread.VolatileRead which emits a full-fence + + // From http://msdn.microsoft.com/en-us/magazine/jj863136.aspx + // An operation that’s closely related to Interlocked methods is Thread.MemoryBarrier, which can be thought of as a dummy Interlocked operation. + // Just like an Interlocked method, Thread.Memory­Barrier can’t be reordered with any prior or subsequent memory operations. Unlike an Interlocked + // method, though, Thread.MemoryBarrier has no side effect; it simply constrains memory reorderings. + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/Bitwise.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/Bitwise.cs new file mode 100644 index 0000000000..79dd960137 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/Bitwise.cs @@ -0,0 +1,90 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; + + +namespace HdrHistogram.Utilities +{ + //Code has been tested and taken from : + //http://stackoverflow.com/questions/9543410/i-dont-think-numberofleadingzeroslong-i-in-long-java-is-based-floorlog2x/9543537#9543537 + //http://stackoverflow.com/questions/21888140/de-bruijn-algorithm-binary-digit-count-64bits-c-sharp/21888542#21888542 + //http://stackoverflow.com/questions/15967240/fastest-implementation-of-log2int-and-log2float + //http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious + // + //Ideally newer versions of .NET will expose the CPU instructions to do this Intel SSE 'lzcnt' (Leading Zero Count), or give access to the BitScanReverse VC++ functions (https://msdn.microsoft.com/en-us/library/fbxyd7zd.aspx) + + /// + /// Exposes optimised methods to get Leading Zero Count. + /// + internal static class Bitwise + { + private static readonly int[] Lookup; + + static Bitwise() + { + Lookup = new int[256]; + for (int i = 1; i < 256; ++i) + { + Lookup[i] = (int)(Math.Log(i) / Math.Log(2)); + } + } + + /// + /// Returns the Leading Zero Count (lzc) of the for its binary representation. + /// + /// The value to find the number of leading zeros + /// The number of leading zeros. + public static int NumberOfLeadingZeros(long value) + { + //Optimisation for 32 bit values. So values under 00:16:41.0 when measuring with Stopwatch.GetTimestamp()*, we will hit a fast path. + // * as at writing on Win10 .NET 4.6 + if (value < int.MaxValue) + return 63 - Log2((int)value); + return NumberOfLeadingZerosLong(value); + } + + private static int NumberOfLeadingZerosLong(long value) + { + // Code from http://stackoverflow.com/questions/9543410/i-dont-think-numberofleadingzeroslong-i-in-long-java-is-based-floorlog2x/9543537#9543537 + + //--Already checked that values here are over int.MaxValue, i.e. !=0 + // HD, Figure 5-6 + //if (value == 0) + // return 64; + var n = 1; + // >>> in Java is a "unsigned bit shift", to do the same in C# we use >> (but it HAS to be an unsigned int) + var x = (uint)(value >> 32); + if (x == 0) { n += 32; x = (uint)value; } + if (x >> 16 == 0) { n += 16; x <<= 16; } + if (x >> 24 == 0) { n += 8; x <<= 8; } + if (x >> 28 == 0) { n += 4; x <<= 4; } + if (x >> 30 == 0) { n += 2; x <<= 2; } + n -= (int)(x >> 31); + return n; + } + + private static int Log2(int i) + { + if (i >= 0x1000000) { return Lookup[i >> 24] + 24; } + if (i >= 0x10000) { return Lookup[i >> 16] + 16; } + if (i >= 0x100) { return Lookup[i >> 8] + 8; } + return Lookup[i]; + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ByteBuffer.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ByteBuffer.cs new file mode 100644 index 0000000000..d01e2c9e7b --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ByteBuffer.cs @@ -0,0 +1,310 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * Written by Matt Warren, and released to the public domain, + * as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + */ + +using System; +using System.Net; + +namespace HdrHistogram.Utilities +{ + // See http://stackoverflow.com/questions/1261543/equivalent-of-javas-bytebuffer-puttype-in-c-sharp + // and http://stackoverflow.com/questions/18040012/what-is-the-equivalent-of-javas-bytebuffer-wrap-in-c + // and http://stackoverflow.com/questions/1261543/equivalent-of-javas-bytebuffer-puttype-in-c-sharp + // Java version http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html + /// + /// A byte buffer that tracks position and allows reads and writes of 32 and 64 bit integer values. + /// + internal sealed class ByteBuffer + { + private readonly byte[] _internalBuffer; + + /// + /// Creates a with a specified capacity in bytes. + /// + /// The capacity of the buffer in bytes + /// A newly created . + public static ByteBuffer Allocate(int bufferCapacity) + { + return new ByteBuffer(bufferCapacity); + } + + /// + /// Creates a loaded with the provided byte array. + /// + /// The source byte array to load the buffer with. + /// A newly created . + public static ByteBuffer Allocate(byte[] source) + { + var buffer = new ByteBuffer(source.Length); + Buffer.BlockCopy(source, 0, buffer._internalBuffer, buffer.Position, source.Length); + return buffer; + } + + private ByteBuffer(int bufferCapacity) + { + _internalBuffer = new byte[bufferCapacity]; + Position = 0; + } + + /// + /// The buffer's current position in the underlying byte array + /// + public int Position { get; set; } + + /// + /// Returns the capacity of the + /// + /// The length of the internal byte array. + public int Capacity() + { + return _internalBuffer.Length; + } + + /// + /// The remaining capacity. + /// + /// The number of bytes between the current position and the underlying byte array length. + public int Remaining() + { + return Capacity() - Position; + } + + /// + /// Reads from the provided , into the buffer. + /// + /// The source stream to read from. + /// The number of bytes to read. + /// The number of bytes read. + public int ReadFrom(System.IO.Stream source, int length) + { + return source.Read(_internalBuffer, Position, length); + } + + /// + /// Gets the current byte and advances the position by one. + /// + /// The byte at the current position. + public byte Get() + { + return _internalBuffer[Position++]; + } + + /// + /// Gets the 16 bit integer () at the current position, and then advances by two. + /// + /// The value of the at the current position. + public short GetShort() + { + var shortValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(_internalBuffer, Position)); + Position += (sizeof(short)); + return shortValue; + } + + /// + /// Gets the 32 bit integer () at the current position, and then advances by four. + /// + /// The value of the at the current position. + public int GetInt() + { + var intValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(_internalBuffer, Position)); + Position += sizeof(int); + return intValue; + } + + /// + /// Gets the 64 bit integer () at the current position, and then advances by eight. + /// + /// The value of the at the current position. + public long GetLong() + { + var longValue = IPAddress.HostToNetworkOrder(BitConverter.ToInt64(_internalBuffer, Position)); + Position += sizeof(long); + return longValue; + } + + /// + /// Gets the double floating point number () at the current position, and then advances by eight. + /// + /// The value of the at the current position. + public double GetDouble() + { + var doubleValue = Int64BitsToDouble(ToInt64(_internalBuffer, Position)); + Position += sizeof(double); + return doubleValue; + } + + /// + /// Converts the specified 64-bit signed integer to a double-precision + /// floating point number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A double-precision floating point number whose value is equivalent to value. + private static double Int64BitsToDouble(long value) + { + return BitConverter.Int64BitsToDouble(value); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit signed integer formed by eight bytes beginning at startIndex. + private static long ToInt64(byte[] value, int startIndex) + { + return CheckedFromBytes(value, startIndex, 8); + } + + /// + /// Checks the arguments for validity before calling FromBytes + /// (which can therefore assume the arguments are valid). + /// + /// The bytes to convert after checking + /// The index of the first byte to convert + /// The number of bytes to convert + /// + private static long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) + { + CheckByteArgument(value, startIndex, bytesToConvert); + return FromBytes(value, startIndex, bytesToConvert); + } + + /// + /// Checks the given argument for validity. + /// + /// The byte array passed in + /// The start index passed in + /// The number of bytes required + /// value is a null reference + /// + /// startIndex is less than zero or greater than the length of value minus bytesRequired. + /// + private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + if (startIndex < 0 || startIndex > value.Length - bytesRequired) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + } + + /// + /// Returns a value built from the specified number of bytes from the given buffer, + /// starting at index. + /// + /// The data in byte array format + /// The first index to use + /// The number of bytes to use + /// The value built from the given bytes + private static long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + { + long ret = 0; + for (int i = 0; i < bytesToConvert; i++) + { + ret = unchecked((ret << 8) | buffer[startIndex + i]); + } + return ret; + } + + /// + /// Writes a byte value to the current position, and advances the position by one. + /// + /// The byte value to write. + public void Put(byte value) + { + _internalBuffer[Position++] = value; + } + + /// + /// Sets the bytes at the current position to the value of the passed value, and advances the position. + /// + /// The value to set the current position to. + public void PutInt(int value) + { + var intAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); + Array.Copy(intAsBytes, 0, _internalBuffer, Position, intAsBytes.Length); + Position += intAsBytes.Length; + } + + /// + /// Sets the bytes at the provided position to the value of the passed value, and does not advance the position. + /// + /// The position to set the value at. + /// The value to set. + /// + /// This can be useful for writing a value into an earlier placeholder e.g. a header property for storing the body length. + /// + public void PutInt(int index, int value) + { + var intAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); + Array.Copy(intAsBytes, 0, _internalBuffer, index, intAsBytes.Length); + // We don't increment the Position as this is an explicit write. + } + + /// + /// Sets the bytes at the current position to the value of the passed value, and advances the position. + /// + /// The value to set the current position to. + public void PutLong(long value) + { + var longAsBytes = BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); + Array.Copy(longAsBytes, 0, _internalBuffer, Position, longAsBytes.Length); + Position += longAsBytes.Length; + } + + /// + /// Sets the bytes at the current position to the value of the passed value, and advances the position. + /// + /// The value to set the current position to. + public void PutDouble(double value) + { + //PutDouble(ix(CheckIndex(i, (1 << 3))), x); + var doubleAsBytes = BitConverter.GetBytes(value); + Array.Reverse(doubleAsBytes); + Array.Copy(doubleAsBytes, 0, _internalBuffer, Position, doubleAsBytes.Length); + Position += doubleAsBytes.Length; + } + + /// + /// Gets a copy of the internal byte array. + /// + /// The a copy of the internal byte array. + internal byte[] ToArray() + { + var copy = new byte[_internalBuffer.Length]; + Array.Copy(_internalBuffer, copy, _internalBuffer.Length); + return copy; + } + + internal void BlockCopy(Array src, int srcOffset, int dstOffset, int count) + { + Buffer.BlockCopy(src: src, srcOffset: srcOffset, dst: _internalBuffer, dstOffset: dstOffset, count: count); + Position += count; + } + + internal void BlockGet(Array target, int targetOffset, int sourceOffset, int count) + { + Buffer.BlockCopy(src: _internalBuffer, srcOffset: sourceOffset, dst: target, dstOffset: targetOffset, count: count); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ByteBufferExtensions.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ByteBufferExtensions.cs new file mode 100644 index 0000000000..be73b3c17f --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/ByteBufferExtensions.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.IO; +using System.IO.Compression; + +namespace HdrHistogram.Utilities +{ + internal static class ByteBufferExtensions + { + /// + /// Copies compressed contents from into the from the + /// + /// The that will be written to. + /// The source to read the data from. + /// The buffer's offset to start writing from. + /// The number of bytes written. + public static int CompressedCopy(this ByteBuffer target, ByteBuffer source, int targetOffset) + { + byte[] compressed; + using (var ms = new MemoryStream(source.ToArray())) + { + compressed = Compress(ms); + } + target.BlockCopy(compressed, 0, targetOffset, compressed.Length); + return compressed.Length; + } + + private static byte[] Compress(Stream input) + { + using (var compressStream = new MemoryStream()) + { + //Add the RFC 1950 headers. + compressStream.WriteByte(0x58); + compressStream.WriteByte(0x85); + using (var compressor = new DeflateStream(compressStream, CompressionMode.Compress, leaveOpen: true)) + { + input.CopyTo(compressor); + } + return compressStream.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/TypeHelper.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/TypeHelper.cs new file mode 100644 index 0000000000..1851b47fcb --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/TypeHelper.cs @@ -0,0 +1,44 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Linq; +using System.Reflection; + +namespace HdrHistogram.Utilities +{ + internal static class TypeHelper + { + /// + /// Gets the constructor that matches the parameter array. + /// Searches for a public instance constructor whose parameters match the types in the specified array. + /// + /// The type to search. + /// An array of objects representing the number, order and type of the parameters for the desired constructor. + /// The if a match is found, else null. + /// + /// In most versions of .NET this method is provided directly on , however for full support, we provide this ourselves. + /// + public static ConstructorInfo GetConstructor( Type type, Type[] ctorArgTypes) + { + var info = type.GetTypeInfo(); + return info.DeclaredConstructors + .FirstOrDefault(ctor => IsParameterMatch(ctor, ctorArgTypes)); + } + + private static bool IsParameterMatch(ConstructorInfo ctor, Type[] expectedParamters) + { + var ctorParams = ctor.GetParameters(); + return ctorParams.Select(p => p.ParameterType) + .ToArray() + .IsSequenceEqual(expectedParamters); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/UnixTimeExtensions.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/UnixTimeExtensions.cs new file mode 100644 index 0000000000..098e64d445 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/UnixTimeExtensions.cs @@ -0,0 +1,68 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; + +namespace HdrHistogram.Utilities +{ + /// + /// Provides helper methods for working with times in Unix Epoch convention. + /// + internal static class UnixTimeExtensions + { + private const long EpochInTicks = 621355968000000000; + + /// + /// Gets the seconds elapsed since the Unix Epoch (01-Jan-1970 UTC) + /// + /// The source time. + /// Returns the number whole and partial seconds elapsed since the Unix Epoch until the time. + /// Thrown if the Kind is . + public static double SecondsSinceUnixEpoch(this DateTime source) + { + if (source.Kind == DateTimeKind.Unspecified) throw new ArgumentException("DateTime must have kind specified."); + return (source.ToUniversalTime().Ticks - EpochInTicks) / (double)TimeSpan.TicksPerSecond; + } + + /// + /// Gets the milliseconds elapsed since the Unix Epoch (01-Jan-1970 UTC) + /// + /// The source time. + /// Returns the number whole milliseconds elapsed since the Unix Epoch until the time. + /// Thrown if the Kind is . + public static long MillisecondsSinceUnixEpoch(this DateTime source) + { + if (source.Kind == DateTimeKind.Unspecified) throw new ArgumentException("DateTime must have kind specified."); + return (source.ToUniversalTime().Ticks - EpochInTicks) / TimeSpan.TicksPerMillisecond; + } + + /// + /// Returns the date and time specified by the seconds since the Unix Epoch + /// + /// The seconds since epoch + /// A DateTime value in UTC kind. + public static DateTime ToDateFromSecondsSinceEpoch(this double secondsSinceUnixEpoch) + { + var ticks = (long)(secondsSinceUnixEpoch * TimeSpan.TicksPerSecond); + return new DateTime(EpochInTicks + ticks, DateTimeKind.Utc); + } + + /// + /// Returns the date and time specified by the milliseconds since the Unix Epoch + /// + /// The milliseconds since epoch + /// A DateTime value in UTC kind. + public static DateTime ToDateFromMillisecondsSinceEpoch(this long millisecondsSinceUnixEpoch) + { + var ticks = millisecondsSinceUnixEpoch * TimeSpan.TicksPerMillisecond; + return new DateTime(EpochInTicks + ticks, DateTimeKind.Utc); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/WriterReaderPhaser.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/WriterReaderPhaser.cs new file mode 100644 index 0000000000..c0252de1a0 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/Utilities/WriterReaderPhaser.cs @@ -0,0 +1,225 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +/* + * This is a .NET port of the original Java version, which was written by + * Gil Tene as described in + * https://github.com/HdrHistogram/HdrHistogram + * and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace HdrHistogram.Utilities +{ + /// + /// instances provide an asymmetric means for synchronizing the execution of wait-free "writer" critical sections against a "reader phase flip" that needs to make sure no writer critical sections that were active at the beginning of the flip are still active after the flip is done. + /// Multiple writers and multiple readers are supported. + /// + /// + /// + /// While a can be useful in multiple scenarios, a specific and common use case is that of safely managing "double buffered" data stream access. + /// This style of access allows writers to proceed without being blocked, while readers gain access to stable and unchanging buffer samples + /// + /// + ///
+ /// NOTE: writers are wait-free on architectures that support wait-free atomic increment operations. + /// They remain lock-free (but not wait-free) on architectures that do not support wait-free atomic increment operations. + ///
+ /// "writers" are wait free, "readers" block for other "readers", and "readers" are only blocked by "writers" whose critical was entered before the reader's attempt. + /// + /// When used to protect an actively recording data structure, the assumptions on how readers and writers act are: + ///
    + ///
  1. There are two sets of data structures("active" and "inactive")
  2. + ///
  3. Writing is done to the perceived active version(as perceived by the writer), and only within critical sections delineated by and ).
  4. + ///
  5. Only readers switch the perceived roles of the active and inactive data structures. + /// They do so only while under , and only before calling .
  6. + ///
+ /// When the above assumptions are met, guarantees that the inactive data structures are not being modified by any writers while being read while under protection after a operation. + ///
+ ///
+ internal class WriterReaderPhaser + { + private readonly object _readerLock = new object(); + + private long _startEpoch = 0; + private long _evenEndEpoch = 0; + private long _oddEndEpoch = long.MinValue; + + private static long GetAndIncrement(ref long value) + { + var updatedValue = Interlocked.Increment(ref value); + return updatedValue - 1; //previous value; + } + private static long GetAndSet(ref long value, long newValue) + { + return Interlocked.Exchange(ref value, newValue); + } + private static void LazySet(ref long value, long newValue) + { + Interlocked.Exchange(ref value, newValue); + } + + /// + /// Indicate entry to a critical section containing a write operation. + /// + /// + /// an (opaque) value associated with the critical section entry, + /// which MUST be provided to the matching call. + /// + /// + /// + /// This call is wait-free on architectures that support wait free atomic increment operations, + /// and is lock-free on architectures that do not. + /// + /// + /// must be matched with a subsequent + /// in order for CriticalSectionPhaser synchronization to function properly. + /// + /// + /// The pattern could have been used but was not due to the high allocation count it would have incurred. + /// + /// + public long WriterCriticalSectionEnter() + { + return GetAndIncrement(ref _startEpoch); + } + + /// + /// Indicate exit from a critical section containing a write operation. + /// + /// the opaque value (token) returned from the matching call. + /// + /// This call is wait-free on architectures that support wait free atomic increment operations, and is lock-free on architectures that do not. + /// + /// must be matched with a preceding call, and must be provided with the matching call's return value, in order for CriticalSectionPhaser synchronization to function properly. + /// + /// + public void WriterCriticalSectionExit(long criticalValueAtEnter) + { + if (criticalValueAtEnter < 0) + { + GetAndIncrement(ref _oddEndEpoch); + } + else + { + GetAndIncrement(ref _evenEndEpoch); + } + } + + /// + /// Enter to a critical section containing a read operation (mutually excludes against other calls). + /// DOES NOT provide synchronization against calls. + /// Use to synchronize reads against writers. + /// + public void ReaderLock() + { + Monitor.Enter(_readerLock); + } + + /// + /// Exit from a critical section containing a read operation(relinquishes mutual exclusion against other calls). + /// + public void ReaderUnlock() + { + Monitor.Exit(_readerLock); + } + + + /// + /// Flip a phase in the instance, can only be called while holding the . + /// + /// The amount of time to sleep in each yield if yield loop is needed. + /// + /// will return only after all writer critical sections (protected by and that may have been in flight when the call were made had completed. + /// + /// No actual writer critical section activity is required for to succeed. + /// + /// + /// However, is lock-free with respect to calls to and . + /// It may spin-wait for for active writer critical section code to complete. + /// + /// + public void FlipPhase(TimeSpan yieldPeriod) + { + if (!Monitor.IsEntered(_readerLock)) + { + throw new InvalidOperationException("FlipPhase can only be called while holding the reader lock"); + } + + var isNextPhaseEven = (_startEpoch < 0); // Current phase is odd... + + long initialStartValue; + // First, clear currently unused [next] phase end epoch (to proper initial value for phase): + if (isNextPhaseEven) + { + initialStartValue = 0; + LazySet(ref _evenEndEpoch, initialStartValue); + } + else + { + initialStartValue = long.MinValue; + LazySet(ref _oddEndEpoch, initialStartValue); + } + + // Next, reset start value, indicating new phase, and retain value at flip: + //long startValueAtFlip = startEpochUpdater.getAndSet(this, initialStartValue); + long startValueAtFlip = GetAndSet(ref _startEpoch, initialStartValue); + + // Now, spin until previous phase end value catches up with start value at flip: + bool caughtUp = false; + do + { + if (isNextPhaseEven) + { + caughtUp = (_oddEndEpoch == startValueAtFlip); + } + else + { + caughtUp = (_evenEndEpoch == startValueAtFlip); + } + if (!caughtUp) + { + //TODO: Revist this and check if a SpinWiat is actually preferable here? -LC + if (yieldPeriod == TimeSpan.Zero) + { + Task.Yield().GetAwaiter().GetResult(); + } + else + { + //Thread.Sleep(yieldPeriod); + Task.Delay(yieldPeriod).GetAwaiter().GetResult(); + } + } + } while (!caughtUp); + } + + /// + /// Flip a phase in the instance, can only be called while holding the . + /// + /// + /// will return only after all writer critical sections (protected by and that may have been in flight when the call were made had completed. + /// + /// No actual writer critical section activity is required for to succeed. + /// + /// + /// However, is lock-free with respect to calls to and . + /// It may spin-wait for for active writer critical section code to complete. + /// + /// + public void FlipPhase() + { + FlipPhase(TimeSpan.Zero); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/ZigZagEncoding.cs b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/ZigZagEncoding.cs new file mode 100644 index 0000000000..10592feef8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/HdrHistogram/ZigZagEncoding.cs @@ -0,0 +1,158 @@ +// ------------------------------------------------------------ +// The code in this repository code was written by Lee Campbell, as a +// derived work from the original Java by Gil Tene of Azul Systems and +// Michael Barker, and released to the public domain, as explained +// at http://creativecommons.org/publicdomain/zero/1.0/ +// ------------------------------------------------------------ + +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Utilities; + +namespace HdrHistogram +{ + /// + /// Exposes methods to write values to a with ZigZag LEB128-64b9B-variant encoding. + /// (Little Endian Base128 Encoding, 64bit value store as a maximum of 9Bytes) + /// + /// + ///

+ /// This class provides encoding and decoding methods for writing and reading ZigZag-encoded LEB128-64b9B-variant(Little Endian Base 128) values to/from a . + /// LEB128's variable length encoding provides for using a smaller number of bytes for smaller values, and the use of ZigZag encoding allows small(closer to zero) negative values to use fewer bytes. + /// Details on both LEB128 and ZigZag can be readily found elsewhere. + ///

+ ///

+ /// The LEB128-64b9B-variant encoding used here diverges from the "original" LEB128 as it extends to 64 bit values. + /// In the original LEB128, a 64 bit value can take up to 10 bytes in the stream, where this variant's encoding of a 64 bit values will max out at 9 bytes. + /// As such, this encoder/decoder should NOT be used for encoding or decoding "standard" LEB128 formats (e.g.Google Protocol Buffers). + ///

+ ///

+ /// ZigZag Encoding explained here - https://gist.github.com/mfuerstenau/ba870a29e16536fdbaba + /// LEB128 explained here - https://en.wikipedia.org/wiki/LEB128 + ///

+ ///
+ internal static class ZigZagEncoding + { + /// + /// Writes a 64 bit integer () value to the given buffer in LEB128-64b9B-variant ZigZag encoded format. + /// + /// The buffer to write to. + /// The value to write to the buffer. + public static void PutLong(ByteBuffer buffer, long value) + { + value = (value << 1) ^ (value >> 63); + if (value >> 7 == 0) + { + buffer.Put((byte)value); + } + else { + buffer.Put((byte)((value & 0x7F) | 0x80)); + if (value >> 14 == 0) + { + buffer.Put((byte)(value >> 7)); + } + else { + buffer.Put((byte)(value >> 7 | 0x80)); + if (value >> 21 == 0) + { + buffer.Put((byte)(value >> 14)); + } + else { + buffer.Put((byte)(value >> 14 | 0x80)); + if (value >> 28 == 0) + { + buffer.Put((byte)(value >> 21)); + } + else { + buffer.Put((byte)(value >> 21 | 0x80)); + if (value >> 35 == 0) + { + buffer.Put((byte)(value >> 28)); + } + else { + buffer.Put((byte)(value >> 28 | 0x80)); + if (value >> 42 == 0) + { + buffer.Put((byte)(value >> 35)); + } + else { + buffer.Put((byte)(value >> 35 | 0x80)); + if (value >> 49 == 0) + { + buffer.Put((byte)(value >> 42)); + } + else { + buffer.Put((byte)(value >> 42 | 0x80)); + if (value >> 56 == 0) + { + buffer.Put((byte)(value >> 49)); + } + else { + buffer.Put((byte)(value >> 49 | 0x80)); + buffer.Put((byte)(value >> 56)); + } + } + } + } + } + } + } + } + } + + /// + /// Reads an LEB128-64b9B ZigZag encoded 64 bit integer () value from the given buffer. + /// + /// The buffer to read from. + /// The value read from the buffer. + public static long GetLong(ByteBuffer buffer) + { + long v = buffer.Get(); + long value = v & 0x7F; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 7; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 14; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 21; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 28; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 35; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 42; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= (v & 0x7F) << 49; + if ((v & 0x80) != 0) + { + v = buffer.Get(); + value |= v << 56; + } + } + } + } + } + } + } + } + var unsignRightShiftedValue = (long)((ulong)value >> 1); + value = unsignRightShiftedValue ^ (-(value & 1)); + return value; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/OSS/cgmanifest.json b/Microsoft.Azure.Cosmos/src/OSS/cgmanifest.json new file mode 100644 index 0000000000..841abffc89 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/OSS/cgmanifest.json @@ -0,0 +1,14 @@ +{ + "Registrations": [ + { + "Component": { + "Type": "git", + "Git": { + "RepositoryUrl": "https://github.com/HdrHistogram/HdrHistogram.NET", + "CommitHash": "58e33440549be69a13a56c60f5e68bbccaae86af" + } + }, + "DevelopmentDependency": false + } + ] +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Pagination/CrossPartitionRangePageAsyncEnumerator.cs b/Microsoft.Azure.Cosmos/src/Pagination/CrossPartitionRangePageAsyncEnumerator.cs index 41d278b963..81dce25a58 100644 --- a/Microsoft.Azure.Cosmos/src/Pagination/CrossPartitionRangePageAsyncEnumerator.cs +++ b/Microsoft.Azure.Cosmos/src/Pagination/CrossPartitionRangePageAsyncEnumerator.cs @@ -125,7 +125,19 @@ public async ValueTask MoveNextAsync(ITrace trace) PartitionRangePageAsyncEnumerator currentPaginator = enumerators.Dequeue(); currentPaginator.SetCancellationToken(this.cancellationToken); - if (!await currentPaginator.MoveNextAsync(childTrace)) + bool moveNextResult = false; + try + { + moveNextResult = await currentPaginator.MoveNextAsync(childTrace); + } + catch + { + // Re-queue the enumerator to avoid emptying the queue + enumerators.Enqueue(currentPaginator); + throw; + } + + if (!moveNextResult) { // Current enumerator is empty, // so recursively retry on the next enumerator. diff --git a/Microsoft.Azure.Cosmos/src/Patch/PatchOperation.cs b/Microsoft.Azure.Cosmos/src/Patch/PatchOperation.cs index 78c9adb872..cba61fb4a5 100644 --- a/Microsoft.Azure.Cosmos/src/Patch/PatchOperation.cs +++ b/Microsoft.Azure.Cosmos/src/Patch/PatchOperation.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.IO; using Newtonsoft.Json; /// @@ -29,9 +30,16 @@ abstract class PatchOperation [JsonProperty(PropertyName = PatchConstants.PropertyNames.Path)] public abstract string Path { get; } - internal virtual bool TrySerializeValueParameter( + /// + /// Serializes the value parameter, if specified for the PatchOperation. + /// + /// Serializer to be used. + /// Outputs the serialized stream if value parameter is specified, null otherwise. + /// True if value is serialized, false otherwise. + /// Output stream should be disposed after use. + public virtual bool TrySerializeValueParameter( CosmosSerializer cosmosSerializer, - out string valueParam) + out Stream valueParam) { valueParam = null; return false; diff --git a/Microsoft.Azure.Cosmos/src/Patch/PatchOperationCore{T}.cs b/Microsoft.Azure.Cosmos/src/Patch/PatchOperationCore{T}.cs index 3c3e9f5f67..91f7ae953b 100644 --- a/Microsoft.Azure.Cosmos/src/Patch/PatchOperationCore{T}.cs +++ b/Microsoft.Azure.Cosmos/src/Patch/PatchOperationCore{T}.cs @@ -33,20 +33,22 @@ public PatchOperationCore( public override string Path { get; } - internal override bool TrySerializeValueParameter( + public override bool TrySerializeValueParameter( CosmosSerializer cosmosSerializer, - out string valueParam) + out Stream valueParam) { - // Use the user serializer so custom conversions are correctly handled - using (Stream stream = cosmosSerializer.ToStream(this.Value)) + // If value is of type Stream, do not serialize + if (typeof(Stream).IsAssignableFrom(typeof(T))) { - using (StreamReader streamReader = new StreamReader(stream)) - { - valueParam = streamReader.ReadToEnd(); - } + valueParam = (Stream)(object)this.Value; + } + else + { + // Use the user serializer so custom conversions are correctly handled + valueParam = cosmosSerializer.ToStream(this.Value); } return true; } } -} +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Patch/PatchOperationsJsonConverter.cs b/Microsoft.Azure.Cosmos/src/Patch/PatchOperationsJsonConverter.cs index be07b3922d..1e943409e4 100644 --- a/Microsoft.Azure.Cosmos/src/Patch/PatchOperationsJsonConverter.cs +++ b/Microsoft.Azure.Cosmos/src/Patch/PatchOperationsJsonConverter.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; + using System.IO; using Newtonsoft.Json; /// @@ -87,8 +88,17 @@ public override void WriteJson( writer.WritePropertyName(PatchConstants.PropertyNames.Path); writer.WriteValue(operation.Path); - if (operation.TrySerializeValueParameter(this.userSerializer, out string valueParam)) + if (operation.TrySerializeValueParameter(this.userSerializer, out Stream valueStream)) { + string valueParam; + using (valueStream) + { + using (StreamReader streamReader = new StreamReader(valueStream)) + { + valueParam = streamReader.ReadToEnd(); + } + } + writer.WritePropertyName(PatchConstants.PropertyNames.Value); writer.WriteRawValue(valueParam); } diff --git a/Microsoft.Azure.Cosmos/src/Patch/PatchOperation{T}.cs b/Microsoft.Azure.Cosmos/src/Patch/PatchOperation{T}.cs index 3ce1eb521d..f716e7b5df 100644 --- a/Microsoft.Azure.Cosmos/src/Patch/PatchOperation{T}.cs +++ b/Microsoft.Azure.Cosmos/src/Patch/PatchOperation{T}.cs @@ -10,12 +10,7 @@ namespace Microsoft.Azure.Cosmos /// Defines PatchOperation with a value parameter. /// /// Data type of value provided for PatchOperation. -#if PREVIEW - public -#else - internal -#endif - abstract class PatchOperation : PatchOperation + internal abstract class PatchOperation : PatchOperation { /// /// Value parameter. diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs index be9b12bce6..bd5adc1b01 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Query.Core.Monads { using System; using System.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing; internal sealed class ExceptionWithStackTraceException : Exception { @@ -75,5 +76,23 @@ private string GetClassName() { return this.GetType().ToString(); } + + public static Exception UnWrapMonadExcepion( + Exception exception, + ITrace trace) + { + if (exception is ExceptionWithStackTraceException exceptionWithStackTrace) + { + return ExceptionWithStackTraceException.UnWrapMonadExcepion(exceptionWithStackTrace.InnerException, trace); + } + + if (!(exception is CosmosOperationCanceledException) + && exception is OperationCanceledException operationCanceledException) + { + return new CosmosOperationCanceledException(operationCanceledException, trace); + } + + return exception; + } } } diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs index 26d0a64c45..97bdfcc1b1 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/CstToAstVisitor.cs @@ -796,7 +796,21 @@ public override SqlObject VisitLogical_scalar_expression([NotNull] sqlParser.Log } else { - throw new NotImplementedException(); + // logical_expression binary_operator logical_expression + Contract.Requires(context.ChildCount == 3); + + SqlScalarExpression left = (SqlScalarExpression)this.Visit(context.logical_scalar_expression(0)); + + if (!CstToAstVisitor.binaryOperatorKindLookup.TryGetValue( + context.children[1].GetText(), + out SqlBinaryScalarOperatorKind operatorKind)) + { + throw new ArgumentOutOfRangeException($"Unknown logical operator: {context.children[1].GetText()}."); + } + + SqlScalarExpression right = (SqlScalarExpression)this.Visit(context.logical_scalar_expression(1)); + + sqlObject = SqlBinaryScalarExpression.Create(operatorKind, left, right); } return sqlObject; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sql.g4 b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sql.g4 index 5e97e20e1b..9b1b325681 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sql.g4 +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sql.g4 @@ -89,6 +89,8 @@ logical_scalar_expression : binary_scalar_expression | in_scalar_expression | like_scalar_expression + | logical_scalar_expression K_AND logical_scalar_expression + | logical_scalar_expression K_OR logical_scalar_expression ; in_scalar_expression @@ -112,8 +114,6 @@ binary_scalar_expression | binary_scalar_expression bitwise_and_operator binary_scalar_expression | binary_scalar_expression bitwise_exclusive_or_operator binary_scalar_expression | binary_scalar_expression bitwise_inclusive_or_operator binary_scalar_expression - | binary_scalar_expression K_AND binary_scalar_expression - | binary_scalar_expression K_OR binary_scalar_expression | binary_scalar_expression string_concat_operator binary_scalar_expression ; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sqlParser.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sqlParser.cs index 7e92c2c20f..a7dcd9b89e 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sqlParser.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Parser/sqlParser.cs @@ -1792,7 +1792,7 @@ private Scalar_expressionContext scalar_expression(int _p) { Context = _localctx; _prevctx = _localctx; - State = 224; logical_scalar_expression(); + State = 224; logical_scalar_expression(0); } break; case 2: @@ -1882,6 +1882,14 @@ public In_scalar_expressionContext in_scalar_expression() { public Like_scalar_expressionContext like_scalar_expression() { return GetRuleContext(0); } + public Logical_scalar_expressionContext[] logical_scalar_expression() { + return GetRuleContexts(); + } + public Logical_scalar_expressionContext logical_scalar_expression(int i) { + return GetRuleContext(i); + } + public ITerminalNode K_AND() { return GetToken(sqlParser.K_AND, 0); } + public ITerminalNode K_OR() { return GetToken(sqlParser.K_OR, 0); } public Logical_scalar_expressionContext(ParserRuleContext parent, int invokingState) : base(parent, invokingState) { @@ -1904,31 +1912,80 @@ public override TResult Accept(IParseTreeVisitor visitor) { [RuleVersion(0)] public Logical_scalar_expressionContext logical_scalar_expression() { - Logical_scalar_expressionContext _localctx = new Logical_scalar_expressionContext(Context, State); - EnterRule(_localctx, 46, RULE_logical_scalar_expression); + return logical_scalar_expression(0); + } + + private Logical_scalar_expressionContext logical_scalar_expression(int _p) { + ParserRuleContext _parentctx = Context; + int _parentState = State; + Logical_scalar_expressionContext _localctx = new Logical_scalar_expressionContext(Context, _parentState); + Logical_scalar_expressionContext _prevctx = _localctx; + int _startState = 46; + EnterRecursionRule(_localctx, 46, RULE_logical_scalar_expression, _p); try { - State = 253; + int _alt; + EnterOuterAlt(_localctx, 1); + { + State = 254; ErrorHandler.Sync(this); switch ( Interpreter.AdaptivePredict(TokenStream,23,Context) ) { case 1: - EnterOuterAlt(_localctx, 1); { - State = 250; binary_scalar_expression(0); + State = 251; binary_scalar_expression(0); } break; case 2: - EnterOuterAlt(_localctx, 2); { - State = 251; in_scalar_expression(); + State = 252; in_scalar_expression(); } break; case 3: - EnterOuterAlt(_localctx, 3); { - State = 252; like_scalar_expression(); + State = 253; like_scalar_expression(); } break; } + Context.Stop = TokenStream.LT(-1); + State = 264; + ErrorHandler.Sync(this); + _alt = Interpreter.AdaptivePredict(TokenStream,25,Context); + while ( _alt!=2 && _alt!=global::Antlr4.Runtime.Atn.ATN.INVALID_ALT_NUMBER ) { + if ( _alt==1 ) { + if ( ParseListeners!=null ) + TriggerExitRuleEvent(); + _prevctx = _localctx; + { + State = 262; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,24,Context) ) { + case 1: + { + _localctx = new Logical_scalar_expressionContext(_parentctx, _parentState); + PushNewRecursionContext(_localctx, _startState, RULE_logical_scalar_expression); + State = 256; + if (!(Precpred(Context, 2))) throw new FailedPredicateException(this, "Precpred(Context, 2)"); + State = 257; Match(K_AND); + State = 258; logical_scalar_expression(3); + } + break; + case 2: + { + _localctx = new Logical_scalar_expressionContext(_parentctx, _parentState); + PushNewRecursionContext(_localctx, _startState, RULE_logical_scalar_expression); + State = 259; + if (!(Precpred(Context, 1))) throw new FailedPredicateException(this, "Precpred(Context, 1)"); + State = 260; Match(K_OR); + State = 261; logical_scalar_expression(2); + } + break; + } + } + } + State = 266; + ErrorHandler.Sync(this); + _alt = Interpreter.AdaptivePredict(TokenStream,25,Context); + } + } } catch (RecognitionException re) { _localctx.exception = re; @@ -1936,7 +1993,7 @@ public Logical_scalar_expressionContext logical_scalar_expression() { ErrorHandler.Recover(this, re); } finally { - ExitRule(); + UnrollRecursionContexts(_parentctx); } return _localctx; } @@ -1978,20 +2035,20 @@ public In_scalar_expressionContext in_scalar_expression() { try { EnterOuterAlt(_localctx, 1); { - State = 255; binary_scalar_expression(0); - State = 257; + State = 267; binary_scalar_expression(0); + State = 269; ErrorHandler.Sync(this); _la = TokenStream.LA(1); if (_la==K_NOT) { { - State = 256; Match(K_NOT); + State = 268; Match(K_NOT); } } - State = 259; Match(K_IN); - State = 260; Match(T__2); - State = 261; scalar_expression_list(); - State = 262; Match(T__3); + State = 271; Match(K_IN); + State = 272; Match(T__2); + State = 273; scalar_expression_list(); + State = 274; Match(T__3); } } catch (RecognitionException re) { @@ -2045,24 +2102,24 @@ public Like_scalar_expressionContext like_scalar_expression() { try { EnterOuterAlt(_localctx, 1); { - State = 264; binary_scalar_expression(0); - State = 266; + State = 276; binary_scalar_expression(0); + State = 278; ErrorHandler.Sync(this); _la = TokenStream.LA(1); if (_la==K_NOT) { { - State = 265; Match(K_NOT); + State = 277; Match(K_NOT); } } - State = 268; Match(K_LIKE); - State = 269; binary_scalar_expression(0); - State = 271; + State = 280; Match(K_LIKE); + State = 281; binary_scalar_expression(0); + State = 283; ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,26,Context) ) { + switch ( Interpreter.AdaptivePredict(TokenStream,28,Context) ) { case 1: { - State = 270; escape_expression(); + State = 282; escape_expression(); } break; } @@ -2109,8 +2166,8 @@ public Escape_expressionContext escape_expression() { try { EnterOuterAlt(_localctx, 1); { - State = 273; Match(K_ESCAPE); - State = 274; Match(STRING_LITERAL); + State = 285; Match(K_ESCAPE); + State = 286; Match(STRING_LITERAL); } } catch (RecognitionException re) { @@ -2155,8 +2212,6 @@ public Bitwise_exclusive_or_operatorContext bitwise_exclusive_or_operator() { public Bitwise_inclusive_or_operatorContext bitwise_inclusive_or_operator() { return GetRuleContext(0); } - public ITerminalNode K_AND() { return GetToken(sqlParser.K_AND, 0); } - public ITerminalNode K_OR() { return GetToken(sqlParser.K_OR, 0); } public String_concat_operatorContext string_concat_operator() { return GetRuleContext(0); } @@ -2197,127 +2252,107 @@ private Binary_scalar_expressionContext binary_scalar_expression(int _p) { EnterOuterAlt(_localctx, 1); { { - State = 277; unary_scalar_expression(); + State = 289; unary_scalar_expression(); } Context.Stop = TokenStream.LT(-1); - State = 319; + State = 325; ErrorHandler.Sync(this); - _alt = Interpreter.AdaptivePredict(TokenStream,28,Context); + _alt = Interpreter.AdaptivePredict(TokenStream,30,Context); while ( _alt!=2 && _alt!=global::Antlr4.Runtime.Atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( ParseListeners!=null ) TriggerExitRuleEvent(); _prevctx = _localctx; { - State = 317; + State = 323; ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,27,Context) ) { + switch ( Interpreter.AdaptivePredict(TokenStream,29,Context) ) { case 1: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 279; - if (!(Precpred(Context, 10))) throw new FailedPredicateException(this, "Precpred(Context, 10)"); - State = 280; multiplicative_operator(); - State = 281; binary_scalar_expression(11); - } - break; - case 2: - { - _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); - PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 283; - if (!(Precpred(Context, 9))) throw new FailedPredicateException(this, "Precpred(Context, 9)"); - State = 284; additive_operator(); - State = 285; binary_scalar_expression(10); - } - break; - case 3: - { - _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); - PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 287; + State = 291; if (!(Precpred(Context, 8))) throw new FailedPredicateException(this, "Precpred(Context, 8)"); - State = 288; relational_operator(); - State = 289; binary_scalar_expression(9); + State = 292; multiplicative_operator(); + State = 293; binary_scalar_expression(9); } break; - case 4: + case 2: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 291; + State = 295; if (!(Precpred(Context, 7))) throw new FailedPredicateException(this, "Precpred(Context, 7)"); - State = 292; equality_operator(); - State = 293; binary_scalar_expression(8); + State = 296; additive_operator(); + State = 297; binary_scalar_expression(8); } break; - case 5: + case 3: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 295; + State = 299; if (!(Precpred(Context, 6))) throw new FailedPredicateException(this, "Precpred(Context, 6)"); - State = 296; bitwise_and_operator(); - State = 297; binary_scalar_expression(7); + State = 300; relational_operator(); + State = 301; binary_scalar_expression(7); } break; - case 6: + case 4: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 299; + State = 303; if (!(Precpred(Context, 5))) throw new FailedPredicateException(this, "Precpred(Context, 5)"); - State = 300; bitwise_exclusive_or_operator(); - State = 301; binary_scalar_expression(6); + State = 304; equality_operator(); + State = 305; binary_scalar_expression(6); } break; - case 7: + case 5: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 303; + State = 307; if (!(Precpred(Context, 4))) throw new FailedPredicateException(this, "Precpred(Context, 4)"); - State = 304; bitwise_inclusive_or_operator(); - State = 305; binary_scalar_expression(5); + State = 308; bitwise_and_operator(); + State = 309; binary_scalar_expression(5); } break; - case 8: + case 6: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 307; + State = 311; if (!(Precpred(Context, 3))) throw new FailedPredicateException(this, "Precpred(Context, 3)"); - State = 308; Match(K_AND); - State = 309; binary_scalar_expression(4); + State = 312; bitwise_exclusive_or_operator(); + State = 313; binary_scalar_expression(4); } break; - case 9: + case 7: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 310; + State = 315; if (!(Precpred(Context, 2))) throw new FailedPredicateException(this, "Precpred(Context, 2)"); - State = 311; Match(K_OR); - State = 312; binary_scalar_expression(3); + State = 316; bitwise_inclusive_or_operator(); + State = 317; binary_scalar_expression(3); } break; - case 10: + case 8: { _localctx = new Binary_scalar_expressionContext(_parentctx, _parentState); PushNewRecursionContext(_localctx, _startState, RULE_binary_scalar_expression); - State = 313; + State = 319; if (!(Precpred(Context, 1))) throw new FailedPredicateException(this, "Precpred(Context, 1)"); - State = 314; string_concat_operator(); - State = 315; binary_scalar_expression(2); + State = 320; string_concat_operator(); + State = 321; binary_scalar_expression(2); } break; } } } - State = 321; + State = 327; ErrorHandler.Sync(this); - _alt = Interpreter.AdaptivePredict(TokenStream,28,Context); + _alt = Interpreter.AdaptivePredict(TokenStream,30,Context); } } } @@ -2361,7 +2396,7 @@ public Multiplicative_operatorContext multiplicative_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 322; + State = 328; _la = TokenStream.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__0) | (1L << T__10) | (1L << T__11))) != 0)) ) { ErrorHandler.RecoverInline(this); @@ -2412,7 +2447,7 @@ public Additive_operatorContext additive_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 324; + State = 330; _la = TokenStream.LA(1); if ( !(_la==T__12 || _la==T__13) ) { ErrorHandler.RecoverInline(this); @@ -2463,7 +2498,7 @@ public Relational_operatorContext relational_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 326; + State = 332; _la = TokenStream.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__14) | (1L << T__15) | (1L << T__16) | (1L << T__17))) != 0)) ) { ErrorHandler.RecoverInline(this); @@ -2514,7 +2549,7 @@ public Equality_operatorContext equality_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 328; + State = 334; _la = TokenStream.LA(1); if ( !(_la==T__18 || _la==T__19) ) { ErrorHandler.RecoverInline(this); @@ -2564,7 +2599,7 @@ public Bitwise_and_operatorContext bitwise_and_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 330; Match(T__20); + State = 336; Match(T__20); } } catch (RecognitionException re) { @@ -2606,7 +2641,7 @@ public Bitwise_exclusive_or_operatorContext bitwise_exclusive_or_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 332; Match(T__21); + State = 338; Match(T__21); } } catch (RecognitionException re) { @@ -2648,7 +2683,7 @@ public Bitwise_inclusive_or_operatorContext bitwise_inclusive_or_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 334; Match(T__22); + State = 340; Match(T__22); } } catch (RecognitionException re) { @@ -2690,7 +2725,7 @@ public String_concat_operatorContext string_concat_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 336; Match(T__23); + State = 342; Match(T__23); } } catch (RecognitionException re) { @@ -2739,7 +2774,7 @@ public Unary_scalar_expressionContext unary_scalar_expression() { Unary_scalar_expressionContext _localctx = new Unary_scalar_expressionContext(Context, State); EnterRule(_localctx, 72, RULE_unary_scalar_expression); try { - State = 342; + State = 348; ErrorHandler.Sync(this); switch (TokenStream.LA(1)) { case T__2: @@ -2758,7 +2793,7 @@ public Unary_scalar_expressionContext unary_scalar_expression() { case PARAMETER: EnterOuterAlt(_localctx, 1); { - State = 338; primary_expression(0); + State = 344; primary_expression(0); } break; case T__12: @@ -2767,8 +2802,8 @@ public Unary_scalar_expressionContext unary_scalar_expression() { case K_NOT: EnterOuterAlt(_localctx, 2); { - State = 339; unary_operator(); - State = 340; unary_scalar_expression(); + State = 345; unary_operator(); + State = 346; unary_scalar_expression(); } break; default: @@ -2816,7 +2851,7 @@ public Unary_operatorContext unary_operator() { try { EnterOuterAlt(_localctx, 1); { - State = 344; + State = 350; _la = TokenStream.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__12) | (1L << T__13) | (1L << T__24) | (1L << K_NOT))) != 0)) ) { ErrorHandler.RecoverInline(this); @@ -3100,16 +3135,16 @@ private Primary_expressionContext primary_expression(int _p) { int _alt; EnterOuterAlt(_localctx, 1); { - State = 388; + State = 394; ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,34,Context) ) { + switch ( Interpreter.AdaptivePredict(TokenStream,36,Context) ) { case 1: { _localctx = new PropertyRefScalarExpressionBaseContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 347; Match(IDENTIFIER); + State = 353; Match(IDENTIFIER); } break; case 2: @@ -3117,7 +3152,7 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new ParameterRefScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 348; Match(PARAMETER); + State = 354; Match(PARAMETER); } break; case 3: @@ -3125,7 +3160,7 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new LiteralScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 349; literal(); + State = 355; literal(); } break; case 4: @@ -3133,17 +3168,17 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new ArrayCreateScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 350; Match(T__5); - State = 352; + State = 356; Match(T__5); + State = 358; ErrorHandler.Sync(this); _la = TokenStream.LA(1); if ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__2) | (1L << T__5) | (1L << T__12) | (1L << T__13) | (1L << T__24) | (1L << T__25) | (1L << K_ARRAY) | (1L << K_EXISTS) | (1L << K_FALSE) | (1L << K_NOT) | (1L << K_NULL) | (1L << K_TRUE) | (1L << K_UDF) | (1L << K_UNDEFINED) | (1L << NUMERIC_LITERAL) | (1L << STRING_LITERAL) | (1L << IDENTIFIER) | (1L << PARAMETER))) != 0)) { { - State = 351; scalar_expression_list(); + State = 357; scalar_expression_list(); } } - State = 354; Match(T__6); + State = 360; Match(T__6); } break; case 5: @@ -3151,17 +3186,17 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new ObjectCreateScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 355; Match(T__25); - State = 357; + State = 361; Match(T__25); + State = 363; ErrorHandler.Sync(this); _la = TokenStream.LA(1); if (_la==STRING_LITERAL) { { - State = 356; object_property_list(); + State = 362; object_property_list(); } } - State = 359; Match(T__26); + State = 365; Match(T__26); } break; case 6: @@ -3169,28 +3204,28 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new FunctionCallScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 362; + State = 368; ErrorHandler.Sync(this); _la = TokenStream.LA(1); if (_la==K_UDF) { { - State = 360; Match(K_UDF); - State = 361; Match(T__4); + State = 366; Match(K_UDF); + State = 367; Match(T__4); } } - State = 364; Match(IDENTIFIER); - State = 365; Match(T__2); - State = 367; + State = 370; Match(IDENTIFIER); + State = 371; Match(T__2); + State = 373; ErrorHandler.Sync(this); _la = TokenStream.LA(1); if ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << T__2) | (1L << T__5) | (1L << T__12) | (1L << T__13) | (1L << T__24) | (1L << T__25) | (1L << K_ARRAY) | (1L << K_EXISTS) | (1L << K_FALSE) | (1L << K_NOT) | (1L << K_NULL) | (1L << K_TRUE) | (1L << K_UDF) | (1L << K_UNDEFINED) | (1L << NUMERIC_LITERAL) | (1L << STRING_LITERAL) | (1L << IDENTIFIER) | (1L << PARAMETER))) != 0)) { { - State = 366; scalar_expression_list(); + State = 372; scalar_expression_list(); } } - State = 369; Match(T__3); + State = 375; Match(T__3); } break; case 7: @@ -3198,9 +3233,9 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new ParenthesizedScalarExperessionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 370; Match(T__2); - State = 371; scalar_expression(0); - State = 372; Match(T__3); + State = 376; Match(T__2); + State = 377; scalar_expression(0); + State = 378; Match(T__3); } break; case 8: @@ -3208,9 +3243,9 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new SubqueryScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 374; Match(T__2); - State = 375; sql_query(); - State = 376; Match(T__3); + State = 380; Match(T__2); + State = 381; sql_query(); + State = 382; Match(T__3); } break; case 9: @@ -3218,10 +3253,10 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new ExistsScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 378; Match(K_EXISTS); - State = 379; Match(T__2); - State = 380; sql_query(); - State = 381; Match(T__3); + State = 384; Match(K_EXISTS); + State = 385; Match(T__2); + State = 386; sql_query(); + State = 387; Match(T__3); } break; case 10: @@ -3229,53 +3264,53 @@ private Primary_expressionContext primary_expression(int _p) { _localctx = new ArrayScalarExpressionContext(_localctx); Context = _localctx; _prevctx = _localctx; - State = 383; Match(K_ARRAY); - State = 384; Match(T__2); - State = 385; sql_query(); - State = 386; Match(T__3); + State = 389; Match(K_ARRAY); + State = 390; Match(T__2); + State = 391; sql_query(); + State = 392; Match(T__3); } break; } Context.Stop = TokenStream.LT(-1); - State = 400; + State = 406; ErrorHandler.Sync(this); - _alt = Interpreter.AdaptivePredict(TokenStream,36,Context); + _alt = Interpreter.AdaptivePredict(TokenStream,38,Context); while ( _alt!=2 && _alt!=global::Antlr4.Runtime.Atn.ATN.INVALID_ALT_NUMBER ) { if ( _alt==1 ) { if ( ParseListeners!=null ) TriggerExitRuleEvent(); _prevctx = _localctx; { - State = 398; + State = 404; ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,35,Context) ) { + switch ( Interpreter.AdaptivePredict(TokenStream,37,Context) ) { case 1: { _localctx = new PropertyRefScalarExpressionRecursiveContext(new Primary_expressionContext(_parentctx, _parentState)); PushNewRecursionContext(_localctx, _startState, RULE_primary_expression); - State = 390; + State = 396; if (!(Precpred(Context, 4))) throw new FailedPredicateException(this, "Precpred(Context, 4)"); - State = 391; Match(T__4); - State = 392; Match(IDENTIFIER); + State = 397; Match(T__4); + State = 398; Match(IDENTIFIER); } break; case 2: { _localctx = new MemberIndexerScalarExpressionContext(new Primary_expressionContext(_parentctx, _parentState)); PushNewRecursionContext(_localctx, _startState, RULE_primary_expression); - State = 393; + State = 399; if (!(Precpred(Context, 3))) throw new FailedPredicateException(this, "Precpred(Context, 3)"); - State = 394; Match(T__5); - State = 395; scalar_expression(0); - State = 396; Match(T__6); + State = 400; Match(T__5); + State = 401; scalar_expression(0); + State = 402; Match(T__6); } break; } } } - State = 402; + State = 408; ErrorHandler.Sync(this); - _alt = Interpreter.AdaptivePredict(TokenStream,36,Context); + _alt = Interpreter.AdaptivePredict(TokenStream,38,Context); } } } @@ -3325,18 +3360,18 @@ public Scalar_expression_listContext scalar_expression_list() { try { EnterOuterAlt(_localctx, 1); { - State = 403; scalar_expression(0); - State = 408; + State = 409; scalar_expression(0); + State = 414; ErrorHandler.Sync(this); _la = TokenStream.LA(1); while (_la==T__1) { { { - State = 404; Match(T__1); - State = 405; scalar_expression(0); + State = 410; Match(T__1); + State = 411; scalar_expression(0); } } - State = 410; + State = 416; ErrorHandler.Sync(this); _la = TokenStream.LA(1); } @@ -3388,18 +3423,18 @@ public Object_property_listContext object_property_list() { try { EnterOuterAlt(_localctx, 1); { - State = 411; object_property(); - State = 416; + State = 417; object_property(); + State = 422; ErrorHandler.Sync(this); _la = TokenStream.LA(1); while (_la==T__1) { { { - State = 412; Match(T__1); - State = 413; object_property(); + State = 418; Match(T__1); + State = 419; object_property(); } } - State = 418; + State = 424; ErrorHandler.Sync(this); _la = TokenStream.LA(1); } @@ -3448,9 +3483,9 @@ public Object_propertyContext object_property() { try { EnterOuterAlt(_localctx, 1); { - State = 419; Match(STRING_LITERAL); - State = 420; Match(T__8); - State = 421; scalar_expression(0); + State = 425; Match(STRING_LITERAL); + State = 426; Match(T__8); + State = 427; scalar_expression(0); } } catch (RecognitionException re) { @@ -3499,7 +3534,7 @@ public LiteralContext literal() { try { EnterOuterAlt(_localctx, 1); { - State = 423; + State = 429; _la = TokenStream.LA(1); if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << K_FALSE) | (1L << K_NULL) | (1L << K_TRUE) | (1L << K_UNDEFINED) | (1L << NUMERIC_LITERAL) | (1L << STRING_LITERAL))) != 0)) ) { ErrorHandler.RecoverInline(this); @@ -3526,6 +3561,7 @@ public override bool Sempred(RuleContext _localctx, int ruleIndex, int predIndex case 10: return collection_expression_sempred((Collection_expressionContext)_localctx, predIndex); case 12: return path_expression_sempred((Path_expressionContext)_localctx, predIndex); case 22: return scalar_expression_sempred((Scalar_expressionContext)_localctx, predIndex); + case 23: return logical_scalar_expression_sempred((Logical_scalar_expressionContext)_localctx, predIndex); case 27: return binary_scalar_expression_sempred((Binary_scalar_expressionContext)_localctx, predIndex); case 38: return primary_expression_sempred((Primary_expressionContext)_localctx, predIndex); } @@ -3552,10 +3588,15 @@ private bool scalar_expression_sempred(Scalar_expressionContext _localctx, int p } return true; } + private bool logical_scalar_expression_sempred(Logical_scalar_expressionContext _localctx, int predIndex) { + switch (predIndex) { + case 6: return Precpred(Context, 2); + case 7: return Precpred(Context, 1); + } + return true; + } private bool binary_scalar_expression_sempred(Binary_scalar_expressionContext _localctx, int predIndex) { switch (predIndex) { - case 6: return Precpred(Context, 10); - case 7: return Precpred(Context, 9); case 8: return Precpred(Context, 8); case 9: return Precpred(Context, 7); case 10: return Precpred(Context, 6); @@ -3577,7 +3618,7 @@ private bool primary_expression_sempred(Primary_expressionContext _localctx, int private static char[] _serializedATN = { '\x3', '\x608B', '\xA72A', '\x8133', '\xB9ED', '\x417C', '\x3BE7', '\x7786', - '\x5964', '\x3', '?', '\x1AC', '\x4', '\x2', '\t', '\x2', '\x4', '\x3', + '\x5964', '\x3', '?', '\x1B2', '\x4', '\x2', '\t', '\x2', '\x4', '\x3', '\t', '\x3', '\x4', '\x4', '\t', '\x4', '\x4', '\x5', '\t', '\x5', '\x4', '\x6', '\t', '\x6', '\x4', '\a', '\t', '\a', '\x4', '\b', '\t', '\b', '\x4', '\t', '\t', '\t', '\x4', '\n', '\t', '\n', '\x4', '\v', '\t', '\v', @@ -3627,316 +3668,321 @@ private bool primary_expression_sempred(Primary_expressionContext _localctx, int '\x18', '\x3', '\x18', '\x3', '\x18', '\x3', '\x18', '\x3', '\x18', '\x3', '\x18', '\x3', '\x18', '\x3', '\x18', '\a', '\x18', '\xF8', '\n', '\x18', '\f', '\x18', '\xE', '\x18', '\xFB', '\v', '\x18', '\x3', '\x19', '\x3', - '\x19', '\x3', '\x19', '\x5', '\x19', '\x100', '\n', '\x19', '\x3', '\x1A', - '\x3', '\x1A', '\x5', '\x1A', '\x104', '\n', '\x1A', '\x3', '\x1A', '\x3', - '\x1A', '\x3', '\x1A', '\x3', '\x1A', '\x3', '\x1A', '\x3', '\x1B', '\x3', - '\x1B', '\x5', '\x1B', '\x10D', '\n', '\x1B', '\x3', '\x1B', '\x3', '\x1B', - '\x3', '\x1B', '\x5', '\x1B', '\x112', '\n', '\x1B', '\x3', '\x1C', '\x3', - '\x1C', '\x3', '\x1C', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', + '\x19', '\x3', '\x19', '\x3', '\x19', '\x5', '\x19', '\x101', '\n', '\x19', + '\x3', '\x19', '\x3', '\x19', '\x3', '\x19', '\x3', '\x19', '\x3', '\x19', + '\x3', '\x19', '\a', '\x19', '\x109', '\n', '\x19', '\f', '\x19', '\xE', + '\x19', '\x10C', '\v', '\x19', '\x3', '\x1A', '\x3', '\x1A', '\x5', '\x1A', + '\x110', '\n', '\x1A', '\x3', '\x1A', '\x3', '\x1A', '\x3', '\x1A', '\x3', + '\x1A', '\x3', '\x1A', '\x3', '\x1B', '\x3', '\x1B', '\x5', '\x1B', '\x119', + '\n', '\x1B', '\x3', '\x1B', '\x3', '\x1B', '\x3', '\x1B', '\x5', '\x1B', + '\x11E', '\n', '\x1B', '\x3', '\x1C', '\x3', '\x1C', '\x3', '\x1C', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', - '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', - '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\a', '\x1D', '\x140', '\n', '\x1D', - '\f', '\x1D', '\xE', '\x1D', '\x143', '\v', '\x1D', '\x3', '\x1E', '\x3', - '\x1E', '\x3', '\x1F', '\x3', '\x1F', '\x3', ' ', '\x3', ' ', '\x3', '!', - '\x3', '!', '\x3', '\"', '\x3', '\"', '\x3', '#', '\x3', '#', '\x3', '$', - '\x3', '$', '\x3', '%', '\x3', '%', '\x3', '&', '\x3', '&', '\x3', '&', - '\x3', '&', '\x5', '&', '\x159', '\n', '&', '\x3', '\'', '\x3', '\'', - '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', - '\x5', '(', '\x163', '\n', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x5', - '(', '\x168', '\n', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x5', '(', - '\x16D', '\n', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x5', '(', '\x172', - '\n', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', + '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\x3', '\x1D', '\a', + '\x1D', '\x146', '\n', '\x1D', '\f', '\x1D', '\xE', '\x1D', '\x149', '\v', + '\x1D', '\x3', '\x1E', '\x3', '\x1E', '\x3', '\x1F', '\x3', '\x1F', '\x3', + ' ', '\x3', ' ', '\x3', '!', '\x3', '!', '\x3', '\"', '\x3', '\"', '\x3', + '#', '\x3', '#', '\x3', '$', '\x3', '$', '\x3', '%', '\x3', '%', '\x3', + '&', '\x3', '&', '\x3', '&', '\x3', '&', '\x5', '&', '\x15F', '\n', '&', + '\x3', '\'', '\x3', '\'', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', + '\x3', '(', '\x3', '(', '\x5', '(', '\x169', '\n', '(', '\x3', '(', '\x3', + '(', '\x3', '(', '\x5', '(', '\x16E', '\n', '(', '\x3', '(', '\x3', '(', + '\x3', '(', '\x5', '(', '\x173', '\n', '(', '\x3', '(', '\x3', '(', '\x3', + '(', '\x5', '(', '\x178', '\n', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', - '\x3', '(', '\x3', '(', '\x5', '(', '\x187', '\n', '(', '\x3', '(', '\x3', + '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x5', '(', '\x18D', '\n', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', '(', '\x3', - '(', '\a', '(', '\x191', '\n', '(', '\f', '(', '\xE', '(', '\x194', '\v', - '(', '\x3', ')', '\x3', ')', '\x3', ')', '\a', ')', '\x199', '\n', ')', - '\f', ')', '\xE', ')', '\x19C', '\v', ')', '\x3', '*', '\x3', '*', '\x3', - '*', '\a', '*', '\x1A1', '\n', '*', '\f', '*', '\xE', '*', '\x1A4', '\v', - '*', '\x3', '+', '\x3', '+', '\x3', '+', '\x3', '+', '\x3', ',', '\x3', - ',', '\x3', ',', '\x2', '\a', '\x16', '\x1A', '.', '\x38', 'N', '-', '\x2', - '\x4', '\x6', '\b', '\n', '\f', '\xE', '\x10', '\x12', '\x14', '\x16', - '\x18', '\x1A', '\x1C', '\x1E', ' ', '\"', '$', '&', '(', '*', ',', '.', - '\x30', '\x32', '\x34', '\x36', '\x38', ':', '<', '>', '@', '\x42', '\x44', - '\x46', 'H', 'J', 'L', 'N', 'P', 'R', 'T', 'V', '\x2', '\n', '\x4', '\x2', - '<', '<', '?', '?', '\x4', '\x2', '!', '!', '$', '$', '\x4', '\x2', '\x3', - '\x3', '\r', '\xE', '\x3', '\x2', '\xF', '\x10', '\x3', '\x2', '\x11', - '\x14', '\x3', '\x2', '\x15', '\x16', '\x5', '\x2', '\xF', '\x10', '\x1B', - '\x1B', '/', '/', '\a', '\x2', '(', '(', '\x30', '\x30', '\x36', '\x36', - '\x38', '\x38', '<', '=', '\x2', '\x1BA', '\x2', 'X', '\x3', '\x2', '\x2', - '\x2', '\x4', '[', '\x3', '\x2', '\x2', '\x2', '\x6', 'k', '\x3', '\x2', - '\x2', '\x2', '\b', 't', '\x3', '\x2', '\x2', '\x2', '\n', 'z', '\x3', - '\x2', '\x2', '\x2', '\f', '|', '\x3', '\x2', '\x2', '\x2', '\xE', '~', - '\x3', '\x2', '\x2', '\x2', '\x10', '\x81', '\x3', '\x2', '\x2', '\x2', - '\x12', '\x89', '\x3', '\x2', '\x2', '\x2', '\x14', '\x8E', '\x3', '\x2', - '\x2', '\x2', '\x16', '\x9A', '\x3', '\x2', '\x2', '\x2', '\x18', '\xAC', - '\x3', '\x2', '\x2', '\x2', '\x1A', '\xAE', '\x3', '\x2', '\x2', '\x2', - '\x1C', '\xBF', '\x3', '\x2', '\x2', '\x2', '\x1E', '\xC2', '\x3', '\x2', - '\x2', '\x2', ' ', '\xC6', '\x3', '\x2', '\x2', '\x2', '\"', '\xCA', '\x3', - '\x2', '\x2', '\x2', '$', '\xD2', '\x3', '\x2', '\x2', '\x2', '&', '\xD6', - '\x3', '\x2', '\x2', '\x2', '(', '\xD8', '\x3', '\x2', '\x2', '\x2', '*', - '\xDD', '\x3', '\x2', '\x2', '\x2', ',', '\xDF', '\x3', '\x2', '\x2', - '\x2', '.', '\xEC', '\x3', '\x2', '\x2', '\x2', '\x30', '\xFF', '\x3', - '\x2', '\x2', '\x2', '\x32', '\x101', '\x3', '\x2', '\x2', '\x2', '\x34', - '\x10A', '\x3', '\x2', '\x2', '\x2', '\x36', '\x113', '\x3', '\x2', '\x2', - '\x2', '\x38', '\x116', '\x3', '\x2', '\x2', '\x2', ':', '\x144', '\x3', - '\x2', '\x2', '\x2', '<', '\x146', '\x3', '\x2', '\x2', '\x2', '>', '\x148', - '\x3', '\x2', '\x2', '\x2', '@', '\x14A', '\x3', '\x2', '\x2', '\x2', - '\x42', '\x14C', '\x3', '\x2', '\x2', '\x2', '\x44', '\x14E', '\x3', '\x2', - '\x2', '\x2', '\x46', '\x150', '\x3', '\x2', '\x2', '\x2', 'H', '\x152', - '\x3', '\x2', '\x2', '\x2', 'J', '\x158', '\x3', '\x2', '\x2', '\x2', - 'L', '\x15A', '\x3', '\x2', '\x2', '\x2', 'N', '\x186', '\x3', '\x2', - '\x2', '\x2', 'P', '\x195', '\x3', '\x2', '\x2', '\x2', 'R', '\x19D', - '\x3', '\x2', '\x2', '\x2', 'T', '\x1A5', '\x3', '\x2', '\x2', '\x2', - 'V', '\x1A9', '\x3', '\x2', '\x2', '\x2', 'X', 'Y', '\x5', '\x4', '\x3', - '\x2', 'Y', 'Z', '\a', '\x2', '\x2', '\x3', 'Z', '\x3', '\x3', '\x2', - '\x2', '\x2', '[', ']', '\x5', '\x6', '\x4', '\x2', '\\', '^', '\x5', - '\x14', '\v', '\x2', ']', '\\', '\x3', '\x2', '\x2', '\x2', ']', '^', - '\x3', '\x2', '\x2', '\x2', '^', '`', '\x3', '\x2', '\x2', '\x2', '_', - '\x61', '\x5', '\x1C', '\xF', '\x2', '`', '_', '\x3', '\x2', '\x2', '\x2', - '`', '\x61', '\x3', '\x2', '\x2', '\x2', '\x61', '\x63', '\x3', '\x2', - '\x2', '\x2', '\x62', '\x64', '\x5', '\x1E', '\x10', '\x2', '\x63', '\x62', - '\x3', '\x2', '\x2', '\x2', '\x63', '\x64', '\x3', '\x2', '\x2', '\x2', - '\x64', '\x66', '\x3', '\x2', '\x2', '\x2', '\x65', 'g', '\x5', ' ', '\x11', - '\x2', '\x66', '\x65', '\x3', '\x2', '\x2', '\x2', '\x66', 'g', '\x3', - '\x2', '\x2', '\x2', 'g', 'i', '\x3', '\x2', '\x2', '\x2', 'h', 'j', '\x5', - '(', '\x15', '\x2', 'i', 'h', '\x3', '\x2', '\x2', '\x2', 'i', 'j', '\x3', - '\x2', '\x2', '\x2', 'j', '\x5', '\x3', '\x2', '\x2', '\x2', 'k', 'm', - '\a', '\x34', '\x2', '\x2', 'l', 'n', '\a', '%', '\x2', '\x2', 'm', 'l', - '\x3', '\x2', '\x2', '\x2', 'm', 'n', '\x3', '\x2', '\x2', '\x2', 'n', - 'p', '\x3', '\x2', '\x2', '\x2', 'o', 'q', '\x5', '\b', '\x5', '\x2', - 'p', 'o', '\x3', '\x2', '\x2', '\x2', 'p', 'q', '\x3', '\x2', '\x2', '\x2', - 'q', 'r', '\x3', '\x2', '\x2', '\x2', 'r', 's', '\x5', '\n', '\x6', '\x2', - 's', '\a', '\x3', '\x2', '\x2', '\x2', 't', 'u', '\a', '\x35', '\x2', - '\x2', 'u', 'v', '\t', '\x2', '\x2', '\x2', 'v', '\t', '\x3', '\x2', '\x2', - '\x2', 'w', '{', '\x5', '\f', '\a', '\x2', 'x', '{', '\x5', '\xE', '\b', - '\x2', 'y', '{', '\x5', '\x10', '\t', '\x2', 'z', 'w', '\x3', '\x2', '\x2', - '\x2', 'z', 'x', '\x3', '\x2', '\x2', '\x2', 'z', 'y', '\x3', '\x2', '\x2', - '\x2', '{', '\v', '\x3', '\x2', '\x2', '\x2', '|', '}', '\a', '\x3', '\x2', - '\x2', '}', '\r', '\x3', '\x2', '\x2', '\x2', '~', '\x7F', '\a', '\x39', - '\x2', '\x2', '\x7F', '\x80', '\x5', '.', '\x18', '\x2', '\x80', '\xF', - '\x3', '\x2', '\x2', '\x2', '\x81', '\x86', '\x5', '\x12', '\n', '\x2', - '\x82', '\x83', '\a', '\x4', '\x2', '\x2', '\x83', '\x85', '\x5', '\x12', - '\n', '\x2', '\x84', '\x82', '\x3', '\x2', '\x2', '\x2', '\x85', '\x88', - '\x3', '\x2', '\x2', '\x2', '\x86', '\x84', '\x3', '\x2', '\x2', '\x2', - '\x86', '\x87', '\x3', '\x2', '\x2', '\x2', '\x87', '\x11', '\x3', '\x2', - '\x2', '\x2', '\x88', '\x86', '\x3', '\x2', '\x2', '\x2', '\x89', '\x8C', - '\x5', '.', '\x18', '\x2', '\x8A', '\x8B', '\a', ' ', '\x2', '\x2', '\x8B', - '\x8D', '\a', '>', '\x2', '\x2', '\x8C', '\x8A', '\x3', '\x2', '\x2', - '\x2', '\x8C', '\x8D', '\x3', '\x2', '\x2', '\x2', '\x8D', '\x13', '\x3', - '\x2', '\x2', '\x2', '\x8E', '\x8F', '\a', ')', '\x2', '\x2', '\x8F', - '\x90', '\x5', '\x16', '\f', '\x2', '\x90', '\x15', '\x3', '\x2', '\x2', - '\x2', '\x91', '\x92', '\b', '\f', '\x1', '\x2', '\x92', '\x95', '\x5', - '\x18', '\r', '\x2', '\x93', '\x94', '\a', ' ', '\x2', '\x2', '\x94', - '\x96', '\a', '>', '\x2', '\x2', '\x95', '\x93', '\x3', '\x2', '\x2', - '\x2', '\x95', '\x96', '\x3', '\x2', '\x2', '\x2', '\x96', '\x9B', '\x3', - '\x2', '\x2', '\x2', '\x97', '\x98', '\a', '>', '\x2', '\x2', '\x98', - '\x99', '\a', '+', '\x2', '\x2', '\x99', '\x9B', '\x5', '\x18', '\r', - '\x2', '\x9A', '\x91', '\x3', '\x2', '\x2', '\x2', '\x9A', '\x97', '\x3', - '\x2', '\x2', '\x2', '\x9B', '\xA1', '\x3', '\x2', '\x2', '\x2', '\x9C', - '\x9D', '\f', '\x3', '\x2', '\x2', '\x9D', '\x9E', '\a', ',', '\x2', '\x2', - '\x9E', '\xA0', '\x5', '\x16', '\f', '\x4', '\x9F', '\x9C', '\x3', '\x2', - '\x2', '\x2', '\xA0', '\xA3', '\x3', '\x2', '\x2', '\x2', '\xA1', '\x9F', - '\x3', '\x2', '\x2', '\x2', '\xA1', '\xA2', '\x3', '\x2', '\x2', '\x2', - '\xA2', '\x17', '\x3', '\x2', '\x2', '\x2', '\xA3', '\xA1', '\x3', '\x2', - '\x2', '\x2', '\xA4', '\xA6', '\a', '>', '\x2', '\x2', '\xA5', '\xA7', - '\x5', '\x1A', '\xE', '\x2', '\xA6', '\xA5', '\x3', '\x2', '\x2', '\x2', - '\xA6', '\xA7', '\x3', '\x2', '\x2', '\x2', '\xA7', '\xAD', '\x3', '\x2', - '\x2', '\x2', '\xA8', '\xA9', '\a', '\x5', '\x2', '\x2', '\xA9', '\xAA', - '\x5', '\x4', '\x3', '\x2', '\xAA', '\xAB', '\a', '\x6', '\x2', '\x2', - '\xAB', '\xAD', '\x3', '\x2', '\x2', '\x2', '\xAC', '\xA4', '\x3', '\x2', - '\x2', '\x2', '\xAC', '\xA8', '\x3', '\x2', '\x2', '\x2', '\xAD', '\x19', - '\x3', '\x2', '\x2', '\x2', '\xAE', '\xBC', '\b', '\xE', '\x1', '\x2', - '\xAF', '\xB0', '\f', '\x6', '\x2', '\x2', '\xB0', '\xB1', '\a', '\a', - '\x2', '\x2', '\xB1', '\xBB', '\a', '>', '\x2', '\x2', '\xB2', '\xB3', - '\f', '\x5', '\x2', '\x2', '\xB3', '\xB4', '\a', '\b', '\x2', '\x2', '\xB4', - '\xB5', '\a', '<', '\x2', '\x2', '\xB5', '\xBB', '\a', '\t', '\x2', '\x2', - '\xB6', '\xB7', '\f', '\x4', '\x2', '\x2', '\xB7', '\xB8', '\a', '\b', - '\x2', '\x2', '\xB8', '\xB9', '\a', '=', '\x2', '\x2', '\xB9', '\xBB', - '\a', '\t', '\x2', '\x2', '\xBA', '\xAF', '\x3', '\x2', '\x2', '\x2', - '\xBA', '\xB2', '\x3', '\x2', '\x2', '\x2', '\xBA', '\xB6', '\x3', '\x2', - '\x2', '\x2', '\xBB', '\xBE', '\x3', '\x2', '\x2', '\x2', '\xBC', '\xBA', - '\x3', '\x2', '\x2', '\x2', '\xBC', '\xBD', '\x3', '\x2', '\x2', '\x2', - '\xBD', '\x1B', '\x3', '\x2', '\x2', '\x2', '\xBE', '\xBC', '\x3', '\x2', - '\x2', '\x2', '\xBF', '\xC0', '\a', ':', '\x2', '\x2', '\xC0', '\xC1', - '\x5', '.', '\x18', '\x2', '\xC1', '\x1D', '\x3', '\x2', '\x2', '\x2', - '\xC2', '\xC3', '\a', '*', '\x2', '\x2', '\xC3', '\xC4', '\a', '#', '\x2', - '\x2', '\xC4', '\xC5', '\x5', 'P', ')', '\x2', '\xC5', '\x1F', '\x3', - '\x2', '\x2', '\x2', '\xC6', '\xC7', '\a', '\x33', '\x2', '\x2', '\xC7', - '\xC8', '\a', '#', '\x2', '\x2', '\xC8', '\xC9', '\x5', '\"', '\x12', - '\x2', '\xC9', '!', '\x3', '\x2', '\x2', '\x2', '\xCA', '\xCF', '\x5', - '$', '\x13', '\x2', '\xCB', '\xCC', '\a', '\x4', '\x2', '\x2', '\xCC', - '\xCE', '\x5', '$', '\x13', '\x2', '\xCD', '\xCB', '\x3', '\x2', '\x2', - '\x2', '\xCE', '\xD1', '\x3', '\x2', '\x2', '\x2', '\xCF', '\xCD', '\x3', - '\x2', '\x2', '\x2', '\xCF', '\xD0', '\x3', '\x2', '\x2', '\x2', '\xD0', - '#', '\x3', '\x2', '\x2', '\x2', '\xD1', '\xCF', '\x3', '\x2', '\x2', - '\x2', '\xD2', '\xD4', '\x5', '.', '\x18', '\x2', '\xD3', '\xD5', '\x5', - '&', '\x14', '\x2', '\xD4', '\xD3', '\x3', '\x2', '\x2', '\x2', '\xD4', - '\xD5', '\x3', '\x2', '\x2', '\x2', '\xD5', '%', '\x3', '\x2', '\x2', - '\x2', '\xD6', '\xD7', '\t', '\x3', '\x2', '\x2', '\xD7', '\'', '\x3', - '\x2', '\x2', '\x2', '\xD8', '\xD9', '\a', '\x31', '\x2', '\x2', '\xD9', - '\xDA', '\x5', '*', '\x16', '\x2', '\xDA', '\xDB', '\a', '.', '\x2', '\x2', - '\xDB', '\xDC', '\x5', ',', '\x17', '\x2', '\xDC', ')', '\x3', '\x2', - '\x2', '\x2', '\xDD', '\xDE', '\t', '\x2', '\x2', '\x2', '\xDE', '+', - '\x3', '\x2', '\x2', '\x2', '\xDF', '\xE0', '\t', '\x2', '\x2', '\x2', - '\xE0', '-', '\x3', '\x2', '\x2', '\x2', '\xE1', '\xE2', '\b', '\x18', - '\x1', '\x2', '\xE2', '\xED', '\x5', '\x30', '\x19', '\x2', '\xE3', '\xE5', - '\x5', '\x38', '\x1D', '\x2', '\xE4', '\xE6', '\a', '/', '\x2', '\x2', - '\xE5', '\xE4', '\x3', '\x2', '\x2', '\x2', '\xE5', '\xE6', '\x3', '\x2', - '\x2', '\x2', '\xE6', '\xE7', '\x3', '\x2', '\x2', '\x2', '\xE7', '\xE8', - '\a', '\"', '\x2', '\x2', '\xE8', '\xE9', '\x5', '\x38', '\x1D', '\x2', - '\xE9', '\xEA', '\a', '\x1E', '\x2', '\x2', '\xEA', '\xEB', '\x5', '\x38', - '\x1D', '\x2', '\xEB', '\xED', '\x3', '\x2', '\x2', '\x2', '\xEC', '\xE1', - '\x3', '\x2', '\x2', '\x2', '\xEC', '\xE3', '\x3', '\x2', '\x2', '\x2', - '\xED', '\xF9', '\x3', '\x2', '\x2', '\x2', '\xEE', '\xEF', '\f', '\x6', - '\x2', '\x2', '\xEF', '\xF0', '\a', '\n', '\x2', '\x2', '\xF0', '\xF1', - '\x5', '.', '\x18', '\x2', '\xF1', '\xF2', '\a', '\v', '\x2', '\x2', '\xF2', - '\xF3', '\x5', '.', '\x18', '\a', '\xF3', '\xF8', '\x3', '\x2', '\x2', - '\x2', '\xF4', '\xF5', '\f', '\x5', '\x2', '\x2', '\xF5', '\xF6', '\a', - '\f', '\x2', '\x2', '\xF6', '\xF8', '\x5', '.', '\x18', '\x6', '\xF7', - '\xEE', '\x3', '\x2', '\x2', '\x2', '\xF7', '\xF4', '\x3', '\x2', '\x2', - '\x2', '\xF8', '\xFB', '\x3', '\x2', '\x2', '\x2', '\xF9', '\xF7', '\x3', - '\x2', '\x2', '\x2', '\xF9', '\xFA', '\x3', '\x2', '\x2', '\x2', '\xFA', - '/', '\x3', '\x2', '\x2', '\x2', '\xFB', '\xF9', '\x3', '\x2', '\x2', - '\x2', '\xFC', '\x100', '\x5', '\x38', '\x1D', '\x2', '\xFD', '\x100', - '\x5', '\x32', '\x1A', '\x2', '\xFE', '\x100', '\x5', '\x34', '\x1B', - '\x2', '\xFF', '\xFC', '\x3', '\x2', '\x2', '\x2', '\xFF', '\xFD', '\x3', - '\x2', '\x2', '\x2', '\xFF', '\xFE', '\x3', '\x2', '\x2', '\x2', '\x100', - '\x31', '\x3', '\x2', '\x2', '\x2', '\x101', '\x103', '\x5', '\x38', '\x1D', - '\x2', '\x102', '\x104', '\a', '/', '\x2', '\x2', '\x103', '\x102', '\x3', - '\x2', '\x2', '\x2', '\x103', '\x104', '\x3', '\x2', '\x2', '\x2', '\x104', - '\x105', '\x3', '\x2', '\x2', '\x2', '\x105', '\x106', '\a', '+', '\x2', - '\x2', '\x106', '\x107', '\a', '\x5', '\x2', '\x2', '\x107', '\x108', - '\x5', 'P', ')', '\x2', '\x108', '\x109', '\a', '\x6', '\x2', '\x2', '\x109', - '\x33', '\x3', '\x2', '\x2', '\x2', '\x10A', '\x10C', '\x5', '\x38', '\x1D', - '\x2', '\x10B', '\x10D', '\a', '/', '\x2', '\x2', '\x10C', '\x10B', '\x3', - '\x2', '\x2', '\x2', '\x10C', '\x10D', '\x3', '\x2', '\x2', '\x2', '\x10D', - '\x10E', '\x3', '\x2', '\x2', '\x2', '\x10E', '\x10F', '\a', '-', '\x2', - '\x2', '\x10F', '\x111', '\x5', '\x38', '\x1D', '\x2', '\x110', '\x112', - '\x5', '\x36', '\x1C', '\x2', '\x111', '\x110', '\x3', '\x2', '\x2', '\x2', - '\x111', '\x112', '\x3', '\x2', '\x2', '\x2', '\x112', '\x35', '\x3', - '\x2', '\x2', '\x2', '\x113', '\x114', '\a', '&', '\x2', '\x2', '\x114', - '\x115', '\a', '=', '\x2', '\x2', '\x115', '\x37', '\x3', '\x2', '\x2', - '\x2', '\x116', '\x117', '\b', '\x1D', '\x1', '\x2', '\x117', '\x118', - '\x5', 'J', '&', '\x2', '\x118', '\x141', '\x3', '\x2', '\x2', '\x2', - '\x119', '\x11A', '\f', '\f', '\x2', '\x2', '\x11A', '\x11B', '\x5', ':', - '\x1E', '\x2', '\x11B', '\x11C', '\x5', '\x38', '\x1D', '\r', '\x11C', - '\x140', '\x3', '\x2', '\x2', '\x2', '\x11D', '\x11E', '\f', '\v', '\x2', - '\x2', '\x11E', '\x11F', '\x5', '<', '\x1F', '\x2', '\x11F', '\x120', - '\x5', '\x38', '\x1D', '\f', '\x120', '\x140', '\x3', '\x2', '\x2', '\x2', - '\x121', '\x122', '\f', '\n', '\x2', '\x2', '\x122', '\x123', '\x5', '>', - ' ', '\x2', '\x123', '\x124', '\x5', '\x38', '\x1D', '\v', '\x124', '\x140', - '\x3', '\x2', '\x2', '\x2', '\x125', '\x126', '\f', '\t', '\x2', '\x2', - '\x126', '\x127', '\x5', '@', '!', '\x2', '\x127', '\x128', '\x5', '\x38', - '\x1D', '\n', '\x128', '\x140', '\x3', '\x2', '\x2', '\x2', '\x129', '\x12A', - '\f', '\b', '\x2', '\x2', '\x12A', '\x12B', '\x5', '\x42', '\"', '\x2', - '\x12B', '\x12C', '\x5', '\x38', '\x1D', '\t', '\x12C', '\x140', '\x3', - '\x2', '\x2', '\x2', '\x12D', '\x12E', '\f', '\a', '\x2', '\x2', '\x12E', - '\x12F', '\x5', '\x44', '#', '\x2', '\x12F', '\x130', '\x5', '\x38', '\x1D', - '\b', '\x130', '\x140', '\x3', '\x2', '\x2', '\x2', '\x131', '\x132', - '\f', '\x6', '\x2', '\x2', '\x132', '\x133', '\x5', '\x46', '$', '\x2', - '\x133', '\x134', '\x5', '\x38', '\x1D', '\a', '\x134', '\x140', '\x3', - '\x2', '\x2', '\x2', '\x135', '\x136', '\f', '\x5', '\x2', '\x2', '\x136', - '\x137', '\a', '\x1E', '\x2', '\x2', '\x137', '\x140', '\x5', '\x38', - '\x1D', '\x6', '\x138', '\x139', '\f', '\x4', '\x2', '\x2', '\x139', '\x13A', - '\a', '\x32', '\x2', '\x2', '\x13A', '\x140', '\x5', '\x38', '\x1D', '\x5', - '\x13B', '\x13C', '\f', '\x3', '\x2', '\x2', '\x13C', '\x13D', '\x5', - 'H', '%', '\x2', '\x13D', '\x13E', '\x5', '\x38', '\x1D', '\x4', '\x13E', - '\x140', '\x3', '\x2', '\x2', '\x2', '\x13F', '\x119', '\x3', '\x2', '\x2', - '\x2', '\x13F', '\x11D', '\x3', '\x2', '\x2', '\x2', '\x13F', '\x121', - '\x3', '\x2', '\x2', '\x2', '\x13F', '\x125', '\x3', '\x2', '\x2', '\x2', - '\x13F', '\x129', '\x3', '\x2', '\x2', '\x2', '\x13F', '\x12D', '\x3', - '\x2', '\x2', '\x2', '\x13F', '\x131', '\x3', '\x2', '\x2', '\x2', '\x13F', - '\x135', '\x3', '\x2', '\x2', '\x2', '\x13F', '\x138', '\x3', '\x2', '\x2', - '\x2', '\x13F', '\x13B', '\x3', '\x2', '\x2', '\x2', '\x140', '\x143', - '\x3', '\x2', '\x2', '\x2', '\x141', '\x13F', '\x3', '\x2', '\x2', '\x2', - '\x141', '\x142', '\x3', '\x2', '\x2', '\x2', '\x142', '\x39', '\x3', - '\x2', '\x2', '\x2', '\x143', '\x141', '\x3', '\x2', '\x2', '\x2', '\x144', - '\x145', '\t', '\x4', '\x2', '\x2', '\x145', ';', '\x3', '\x2', '\x2', - '\x2', '\x146', '\x147', '\t', '\x5', '\x2', '\x2', '\x147', '=', '\x3', - '\x2', '\x2', '\x2', '\x148', '\x149', '\t', '\x6', '\x2', '\x2', '\x149', - '?', '\x3', '\x2', '\x2', '\x2', '\x14A', '\x14B', '\t', '\a', '\x2', - '\x2', '\x14B', '\x41', '\x3', '\x2', '\x2', '\x2', '\x14C', '\x14D', - '\a', '\x17', '\x2', '\x2', '\x14D', '\x43', '\x3', '\x2', '\x2', '\x2', - '\x14E', '\x14F', '\a', '\x18', '\x2', '\x2', '\x14F', '\x45', '\x3', - '\x2', '\x2', '\x2', '\x150', '\x151', '\a', '\x19', '\x2', '\x2', '\x151', - 'G', '\x3', '\x2', '\x2', '\x2', '\x152', '\x153', '\a', '\x1A', '\x2', - '\x2', '\x153', 'I', '\x3', '\x2', '\x2', '\x2', '\x154', '\x159', '\x5', - 'N', '(', '\x2', '\x155', '\x156', '\x5', 'L', '\'', '\x2', '\x156', '\x157', - '\x5', 'J', '&', '\x2', '\x157', '\x159', '\x3', '\x2', '\x2', '\x2', - '\x158', '\x154', '\x3', '\x2', '\x2', '\x2', '\x158', '\x155', '\x3', - '\x2', '\x2', '\x2', '\x159', 'K', '\x3', '\x2', '\x2', '\x2', '\x15A', - '\x15B', '\t', '\b', '\x2', '\x2', '\x15B', 'M', '\x3', '\x2', '\x2', - '\x2', '\x15C', '\x15D', '\b', '(', '\x1', '\x2', '\x15D', '\x187', '\a', - '>', '\x2', '\x2', '\x15E', '\x187', '\a', '?', '\x2', '\x2', '\x15F', - '\x187', '\x5', 'V', ',', '\x2', '\x160', '\x162', '\a', '\b', '\x2', - '\x2', '\x161', '\x163', '\x5', 'P', ')', '\x2', '\x162', '\x161', '\x3', - '\x2', '\x2', '\x2', '\x162', '\x163', '\x3', '\x2', '\x2', '\x2', '\x163', - '\x164', '\x3', '\x2', '\x2', '\x2', '\x164', '\x187', '\a', '\t', '\x2', - '\x2', '\x165', '\x167', '\a', '\x1C', '\x2', '\x2', '\x166', '\x168', - '\x5', 'R', '*', '\x2', '\x167', '\x166', '\x3', '\x2', '\x2', '\x2', - '\x167', '\x168', '\x3', '\x2', '\x2', '\x2', '\x168', '\x169', '\x3', - '\x2', '\x2', '\x2', '\x169', '\x187', '\a', '\x1D', '\x2', '\x2', '\x16A', - '\x16B', '\a', '\x37', '\x2', '\x2', '\x16B', '\x16D', '\a', '\a', '\x2', - '\x2', '\x16C', '\x16A', '\x3', '\x2', '\x2', '\x2', '\x16C', '\x16D', - '\x3', '\x2', '\x2', '\x2', '\x16D', '\x16E', '\x3', '\x2', '\x2', '\x2', - '\x16E', '\x16F', '\a', '>', '\x2', '\x2', '\x16F', '\x171', '\a', '\x5', - '\x2', '\x2', '\x170', '\x172', '\x5', 'P', ')', '\x2', '\x171', '\x170', - '\x3', '\x2', '\x2', '\x2', '\x171', '\x172', '\x3', '\x2', '\x2', '\x2', - '\x172', '\x173', '\x3', '\x2', '\x2', '\x2', '\x173', '\x187', '\a', - '\x6', '\x2', '\x2', '\x174', '\x175', '\a', '\x5', '\x2', '\x2', '\x175', - '\x176', '\x5', '.', '\x18', '\x2', '\x176', '\x177', '\a', '\x6', '\x2', - '\x2', '\x177', '\x187', '\x3', '\x2', '\x2', '\x2', '\x178', '\x179', - '\a', '\x5', '\x2', '\x2', '\x179', '\x17A', '\x5', '\x4', '\x3', '\x2', - '\x17A', '\x17B', '\a', '\x6', '\x2', '\x2', '\x17B', '\x187', '\x3', - '\x2', '\x2', '\x2', '\x17C', '\x17D', '\a', '\'', '\x2', '\x2', '\x17D', - '\x17E', '\a', '\x5', '\x2', '\x2', '\x17E', '\x17F', '\x5', '\x4', '\x3', - '\x2', '\x17F', '\x180', '\a', '\x6', '\x2', '\x2', '\x180', '\x187', - '\x3', '\x2', '\x2', '\x2', '\x181', '\x182', '\a', '\x1F', '\x2', '\x2', - '\x182', '\x183', '\a', '\x5', '\x2', '\x2', '\x183', '\x184', '\x5', - '\x4', '\x3', '\x2', '\x184', '\x185', '\a', '\x6', '\x2', '\x2', '\x185', - '\x187', '\x3', '\x2', '\x2', '\x2', '\x186', '\x15C', '\x3', '\x2', '\x2', - '\x2', '\x186', '\x15E', '\x3', '\x2', '\x2', '\x2', '\x186', '\x15F', - '\x3', '\x2', '\x2', '\x2', '\x186', '\x160', '\x3', '\x2', '\x2', '\x2', - '\x186', '\x165', '\x3', '\x2', '\x2', '\x2', '\x186', '\x16C', '\x3', - '\x2', '\x2', '\x2', '\x186', '\x174', '\x3', '\x2', '\x2', '\x2', '\x186', - '\x178', '\x3', '\x2', '\x2', '\x2', '\x186', '\x17C', '\x3', '\x2', '\x2', - '\x2', '\x186', '\x181', '\x3', '\x2', '\x2', '\x2', '\x187', '\x192', - '\x3', '\x2', '\x2', '\x2', '\x188', '\x189', '\f', '\x6', '\x2', '\x2', - '\x189', '\x18A', '\a', '\a', '\x2', '\x2', '\x18A', '\x191', '\a', '>', - '\x2', '\x2', '\x18B', '\x18C', '\f', '\x5', '\x2', '\x2', '\x18C', '\x18D', - '\a', '\b', '\x2', '\x2', '\x18D', '\x18E', '\x5', '.', '\x18', '\x2', - '\x18E', '\x18F', '\a', '\t', '\x2', '\x2', '\x18F', '\x191', '\x3', '\x2', - '\x2', '\x2', '\x190', '\x188', '\x3', '\x2', '\x2', '\x2', '\x190', '\x18B', - '\x3', '\x2', '\x2', '\x2', '\x191', '\x194', '\x3', '\x2', '\x2', '\x2', - '\x192', '\x190', '\x3', '\x2', '\x2', '\x2', '\x192', '\x193', '\x3', - '\x2', '\x2', '\x2', '\x193', 'O', '\x3', '\x2', '\x2', '\x2', '\x194', - '\x192', '\x3', '\x2', '\x2', '\x2', '\x195', '\x19A', '\x5', '.', '\x18', - '\x2', '\x196', '\x197', '\a', '\x4', '\x2', '\x2', '\x197', '\x199', - '\x5', '.', '\x18', '\x2', '\x198', '\x196', '\x3', '\x2', '\x2', '\x2', - '\x199', '\x19C', '\x3', '\x2', '\x2', '\x2', '\x19A', '\x198', '\x3', - '\x2', '\x2', '\x2', '\x19A', '\x19B', '\x3', '\x2', '\x2', '\x2', '\x19B', - 'Q', '\x3', '\x2', '\x2', '\x2', '\x19C', '\x19A', '\x3', '\x2', '\x2', - '\x2', '\x19D', '\x1A2', '\x5', 'T', '+', '\x2', '\x19E', '\x19F', '\a', - '\x4', '\x2', '\x2', '\x19F', '\x1A1', '\x5', 'T', '+', '\x2', '\x1A0', - '\x19E', '\x3', '\x2', '\x2', '\x2', '\x1A1', '\x1A4', '\x3', '\x2', '\x2', - '\x2', '\x1A2', '\x1A0', '\x3', '\x2', '\x2', '\x2', '\x1A2', '\x1A3', - '\x3', '\x2', '\x2', '\x2', '\x1A3', 'S', '\x3', '\x2', '\x2', '\x2', - '\x1A4', '\x1A2', '\x3', '\x2', '\x2', '\x2', '\x1A5', '\x1A6', '\a', - '=', '\x2', '\x2', '\x1A6', '\x1A7', '\a', '\v', '\x2', '\x2', '\x1A7', - '\x1A8', '\x5', '.', '\x18', '\x2', '\x1A8', 'U', '\x3', '\x2', '\x2', - '\x2', '\x1A9', '\x1AA', '\t', '\t', '\x2', '\x2', '\x1AA', 'W', '\x3', - '\x2', '\x2', '\x2', ')', ']', '`', '\x63', '\x66', 'i', 'm', 'p', 'z', + '(', '\x3', '(', '\x3', '(', '\a', '(', '\x197', '\n', '(', '\f', '(', + '\xE', '(', '\x19A', '\v', '(', '\x3', ')', '\x3', ')', '\x3', ')', '\a', + ')', '\x19F', '\n', ')', '\f', ')', '\xE', ')', '\x1A2', '\v', ')', '\x3', + '*', '\x3', '*', '\x3', '*', '\a', '*', '\x1A7', '\n', '*', '\f', '*', + '\xE', '*', '\x1AA', '\v', '*', '\x3', '+', '\x3', '+', '\x3', '+', '\x3', + '+', '\x3', ',', '\x3', ',', '\x3', ',', '\x2', '\b', '\x16', '\x1A', + '.', '\x30', '\x38', 'N', '-', '\x2', '\x4', '\x6', '\b', '\n', '\f', + '\xE', '\x10', '\x12', '\x14', '\x16', '\x18', '\x1A', '\x1C', '\x1E', + ' ', '\"', '$', '&', '(', '*', ',', '.', '\x30', '\x32', '\x34', '\x36', + '\x38', ':', '<', '>', '@', '\x42', '\x44', '\x46', 'H', 'J', 'L', 'N', + 'P', 'R', 'T', 'V', '\x2', '\n', '\x4', '\x2', '<', '<', '?', '?', '\x4', + '\x2', '!', '!', '$', '$', '\x4', '\x2', '\x3', '\x3', '\r', '\xE', '\x3', + '\x2', '\xF', '\x10', '\x3', '\x2', '\x11', '\x14', '\x3', '\x2', '\x15', + '\x16', '\x5', '\x2', '\xF', '\x10', '\x1B', '\x1B', '/', '/', '\a', '\x2', + '(', '(', '\x30', '\x30', '\x36', '\x36', '\x38', '\x38', '<', '=', '\x2', + '\x1C0', '\x2', 'X', '\x3', '\x2', '\x2', '\x2', '\x4', '[', '\x3', '\x2', + '\x2', '\x2', '\x6', 'k', '\x3', '\x2', '\x2', '\x2', '\b', 't', '\x3', + '\x2', '\x2', '\x2', '\n', 'z', '\x3', '\x2', '\x2', '\x2', '\f', '|', + '\x3', '\x2', '\x2', '\x2', '\xE', '~', '\x3', '\x2', '\x2', '\x2', '\x10', + '\x81', '\x3', '\x2', '\x2', '\x2', '\x12', '\x89', '\x3', '\x2', '\x2', + '\x2', '\x14', '\x8E', '\x3', '\x2', '\x2', '\x2', '\x16', '\x9A', '\x3', + '\x2', '\x2', '\x2', '\x18', '\xAC', '\x3', '\x2', '\x2', '\x2', '\x1A', + '\xAE', '\x3', '\x2', '\x2', '\x2', '\x1C', '\xBF', '\x3', '\x2', '\x2', + '\x2', '\x1E', '\xC2', '\x3', '\x2', '\x2', '\x2', ' ', '\xC6', '\x3', + '\x2', '\x2', '\x2', '\"', '\xCA', '\x3', '\x2', '\x2', '\x2', '$', '\xD2', + '\x3', '\x2', '\x2', '\x2', '&', '\xD6', '\x3', '\x2', '\x2', '\x2', '(', + '\xD8', '\x3', '\x2', '\x2', '\x2', '*', '\xDD', '\x3', '\x2', '\x2', + '\x2', ',', '\xDF', '\x3', '\x2', '\x2', '\x2', '.', '\xEC', '\x3', '\x2', + '\x2', '\x2', '\x30', '\x100', '\x3', '\x2', '\x2', '\x2', '\x32', '\x10D', + '\x3', '\x2', '\x2', '\x2', '\x34', '\x116', '\x3', '\x2', '\x2', '\x2', + '\x36', '\x11F', '\x3', '\x2', '\x2', '\x2', '\x38', '\x122', '\x3', '\x2', + '\x2', '\x2', ':', '\x14A', '\x3', '\x2', '\x2', '\x2', '<', '\x14C', + '\x3', '\x2', '\x2', '\x2', '>', '\x14E', '\x3', '\x2', '\x2', '\x2', + '@', '\x150', '\x3', '\x2', '\x2', '\x2', '\x42', '\x152', '\x3', '\x2', + '\x2', '\x2', '\x44', '\x154', '\x3', '\x2', '\x2', '\x2', '\x46', '\x156', + '\x3', '\x2', '\x2', '\x2', 'H', '\x158', '\x3', '\x2', '\x2', '\x2', + 'J', '\x15E', '\x3', '\x2', '\x2', '\x2', 'L', '\x160', '\x3', '\x2', + '\x2', '\x2', 'N', '\x18C', '\x3', '\x2', '\x2', '\x2', 'P', '\x19B', + '\x3', '\x2', '\x2', '\x2', 'R', '\x1A3', '\x3', '\x2', '\x2', '\x2', + 'T', '\x1AB', '\x3', '\x2', '\x2', '\x2', 'V', '\x1AF', '\x3', '\x2', + '\x2', '\x2', 'X', 'Y', '\x5', '\x4', '\x3', '\x2', 'Y', 'Z', '\a', '\x2', + '\x2', '\x3', 'Z', '\x3', '\x3', '\x2', '\x2', '\x2', '[', ']', '\x5', + '\x6', '\x4', '\x2', '\\', '^', '\x5', '\x14', '\v', '\x2', ']', '\\', + '\x3', '\x2', '\x2', '\x2', ']', '^', '\x3', '\x2', '\x2', '\x2', '^', + '`', '\x3', '\x2', '\x2', '\x2', '_', '\x61', '\x5', '\x1C', '\xF', '\x2', + '`', '_', '\x3', '\x2', '\x2', '\x2', '`', '\x61', '\x3', '\x2', '\x2', + '\x2', '\x61', '\x63', '\x3', '\x2', '\x2', '\x2', '\x62', '\x64', '\x5', + '\x1E', '\x10', '\x2', '\x63', '\x62', '\x3', '\x2', '\x2', '\x2', '\x63', + '\x64', '\x3', '\x2', '\x2', '\x2', '\x64', '\x66', '\x3', '\x2', '\x2', + '\x2', '\x65', 'g', '\x5', ' ', '\x11', '\x2', '\x66', '\x65', '\x3', + '\x2', '\x2', '\x2', '\x66', 'g', '\x3', '\x2', '\x2', '\x2', 'g', 'i', + '\x3', '\x2', '\x2', '\x2', 'h', 'j', '\x5', '(', '\x15', '\x2', 'i', + 'h', '\x3', '\x2', '\x2', '\x2', 'i', 'j', '\x3', '\x2', '\x2', '\x2', + 'j', '\x5', '\x3', '\x2', '\x2', '\x2', 'k', 'm', '\a', '\x34', '\x2', + '\x2', 'l', 'n', '\a', '%', '\x2', '\x2', 'm', 'l', '\x3', '\x2', '\x2', + '\x2', 'm', 'n', '\x3', '\x2', '\x2', '\x2', 'n', 'p', '\x3', '\x2', '\x2', + '\x2', 'o', 'q', '\x5', '\b', '\x5', '\x2', 'p', 'o', '\x3', '\x2', '\x2', + '\x2', 'p', 'q', '\x3', '\x2', '\x2', '\x2', 'q', 'r', '\x3', '\x2', '\x2', + '\x2', 'r', 's', '\x5', '\n', '\x6', '\x2', 's', '\a', '\x3', '\x2', '\x2', + '\x2', 't', 'u', '\a', '\x35', '\x2', '\x2', 'u', 'v', '\t', '\x2', '\x2', + '\x2', 'v', '\t', '\x3', '\x2', '\x2', '\x2', 'w', '{', '\x5', '\f', '\a', + '\x2', 'x', '{', '\x5', '\xE', '\b', '\x2', 'y', '{', '\x5', '\x10', '\t', + '\x2', 'z', 'w', '\x3', '\x2', '\x2', '\x2', 'z', 'x', '\x3', '\x2', '\x2', + '\x2', 'z', 'y', '\x3', '\x2', '\x2', '\x2', '{', '\v', '\x3', '\x2', + '\x2', '\x2', '|', '}', '\a', '\x3', '\x2', '\x2', '}', '\r', '\x3', '\x2', + '\x2', '\x2', '~', '\x7F', '\a', '\x39', '\x2', '\x2', '\x7F', '\x80', + '\x5', '.', '\x18', '\x2', '\x80', '\xF', '\x3', '\x2', '\x2', '\x2', + '\x81', '\x86', '\x5', '\x12', '\n', '\x2', '\x82', '\x83', '\a', '\x4', + '\x2', '\x2', '\x83', '\x85', '\x5', '\x12', '\n', '\x2', '\x84', '\x82', + '\x3', '\x2', '\x2', '\x2', '\x85', '\x88', '\x3', '\x2', '\x2', '\x2', + '\x86', '\x84', '\x3', '\x2', '\x2', '\x2', '\x86', '\x87', '\x3', '\x2', + '\x2', '\x2', '\x87', '\x11', '\x3', '\x2', '\x2', '\x2', '\x88', '\x86', + '\x3', '\x2', '\x2', '\x2', '\x89', '\x8C', '\x5', '.', '\x18', '\x2', + '\x8A', '\x8B', '\a', ' ', '\x2', '\x2', '\x8B', '\x8D', '\a', '>', '\x2', + '\x2', '\x8C', '\x8A', '\x3', '\x2', '\x2', '\x2', '\x8C', '\x8D', '\x3', + '\x2', '\x2', '\x2', '\x8D', '\x13', '\x3', '\x2', '\x2', '\x2', '\x8E', + '\x8F', '\a', ')', '\x2', '\x2', '\x8F', '\x90', '\x5', '\x16', '\f', + '\x2', '\x90', '\x15', '\x3', '\x2', '\x2', '\x2', '\x91', '\x92', '\b', + '\f', '\x1', '\x2', '\x92', '\x95', '\x5', '\x18', '\r', '\x2', '\x93', + '\x94', '\a', ' ', '\x2', '\x2', '\x94', '\x96', '\a', '>', '\x2', '\x2', + '\x95', '\x93', '\x3', '\x2', '\x2', '\x2', '\x95', '\x96', '\x3', '\x2', + '\x2', '\x2', '\x96', '\x9B', '\x3', '\x2', '\x2', '\x2', '\x97', '\x98', + '\a', '>', '\x2', '\x2', '\x98', '\x99', '\a', '+', '\x2', '\x2', '\x99', + '\x9B', '\x5', '\x18', '\r', '\x2', '\x9A', '\x91', '\x3', '\x2', '\x2', + '\x2', '\x9A', '\x97', '\x3', '\x2', '\x2', '\x2', '\x9B', '\xA1', '\x3', + '\x2', '\x2', '\x2', '\x9C', '\x9D', '\f', '\x3', '\x2', '\x2', '\x9D', + '\x9E', '\a', ',', '\x2', '\x2', '\x9E', '\xA0', '\x5', '\x16', '\f', + '\x4', '\x9F', '\x9C', '\x3', '\x2', '\x2', '\x2', '\xA0', '\xA3', '\x3', + '\x2', '\x2', '\x2', '\xA1', '\x9F', '\x3', '\x2', '\x2', '\x2', '\xA1', + '\xA2', '\x3', '\x2', '\x2', '\x2', '\xA2', '\x17', '\x3', '\x2', '\x2', + '\x2', '\xA3', '\xA1', '\x3', '\x2', '\x2', '\x2', '\xA4', '\xA6', '\a', + '>', '\x2', '\x2', '\xA5', '\xA7', '\x5', '\x1A', '\xE', '\x2', '\xA6', + '\xA5', '\x3', '\x2', '\x2', '\x2', '\xA6', '\xA7', '\x3', '\x2', '\x2', + '\x2', '\xA7', '\xAD', '\x3', '\x2', '\x2', '\x2', '\xA8', '\xA9', '\a', + '\x5', '\x2', '\x2', '\xA9', '\xAA', '\x5', '\x4', '\x3', '\x2', '\xAA', + '\xAB', '\a', '\x6', '\x2', '\x2', '\xAB', '\xAD', '\x3', '\x2', '\x2', + '\x2', '\xAC', '\xA4', '\x3', '\x2', '\x2', '\x2', '\xAC', '\xA8', '\x3', + '\x2', '\x2', '\x2', '\xAD', '\x19', '\x3', '\x2', '\x2', '\x2', '\xAE', + '\xBC', '\b', '\xE', '\x1', '\x2', '\xAF', '\xB0', '\f', '\x6', '\x2', + '\x2', '\xB0', '\xB1', '\a', '\a', '\x2', '\x2', '\xB1', '\xBB', '\a', + '>', '\x2', '\x2', '\xB2', '\xB3', '\f', '\x5', '\x2', '\x2', '\xB3', + '\xB4', '\a', '\b', '\x2', '\x2', '\xB4', '\xB5', '\a', '<', '\x2', '\x2', + '\xB5', '\xBB', '\a', '\t', '\x2', '\x2', '\xB6', '\xB7', '\f', '\x4', + '\x2', '\x2', '\xB7', '\xB8', '\a', '\b', '\x2', '\x2', '\xB8', '\xB9', + '\a', '=', '\x2', '\x2', '\xB9', '\xBB', '\a', '\t', '\x2', '\x2', '\xBA', + '\xAF', '\x3', '\x2', '\x2', '\x2', '\xBA', '\xB2', '\x3', '\x2', '\x2', + '\x2', '\xBA', '\xB6', '\x3', '\x2', '\x2', '\x2', '\xBB', '\xBE', '\x3', + '\x2', '\x2', '\x2', '\xBC', '\xBA', '\x3', '\x2', '\x2', '\x2', '\xBC', + '\xBD', '\x3', '\x2', '\x2', '\x2', '\xBD', '\x1B', '\x3', '\x2', '\x2', + '\x2', '\xBE', '\xBC', '\x3', '\x2', '\x2', '\x2', '\xBF', '\xC0', '\a', + ':', '\x2', '\x2', '\xC0', '\xC1', '\x5', '.', '\x18', '\x2', '\xC1', + '\x1D', '\x3', '\x2', '\x2', '\x2', '\xC2', '\xC3', '\a', '*', '\x2', + '\x2', '\xC3', '\xC4', '\a', '#', '\x2', '\x2', '\xC4', '\xC5', '\x5', + 'P', ')', '\x2', '\xC5', '\x1F', '\x3', '\x2', '\x2', '\x2', '\xC6', '\xC7', + '\a', '\x33', '\x2', '\x2', '\xC7', '\xC8', '\a', '#', '\x2', '\x2', '\xC8', + '\xC9', '\x5', '\"', '\x12', '\x2', '\xC9', '!', '\x3', '\x2', '\x2', + '\x2', '\xCA', '\xCF', '\x5', '$', '\x13', '\x2', '\xCB', '\xCC', '\a', + '\x4', '\x2', '\x2', '\xCC', '\xCE', '\x5', '$', '\x13', '\x2', '\xCD', + '\xCB', '\x3', '\x2', '\x2', '\x2', '\xCE', '\xD1', '\x3', '\x2', '\x2', + '\x2', '\xCF', '\xCD', '\x3', '\x2', '\x2', '\x2', '\xCF', '\xD0', '\x3', + '\x2', '\x2', '\x2', '\xD0', '#', '\x3', '\x2', '\x2', '\x2', '\xD1', + '\xCF', '\x3', '\x2', '\x2', '\x2', '\xD2', '\xD4', '\x5', '.', '\x18', + '\x2', '\xD3', '\xD5', '\x5', '&', '\x14', '\x2', '\xD4', '\xD3', '\x3', + '\x2', '\x2', '\x2', '\xD4', '\xD5', '\x3', '\x2', '\x2', '\x2', '\xD5', + '%', '\x3', '\x2', '\x2', '\x2', '\xD6', '\xD7', '\t', '\x3', '\x2', '\x2', + '\xD7', '\'', '\x3', '\x2', '\x2', '\x2', '\xD8', '\xD9', '\a', '\x31', + '\x2', '\x2', '\xD9', '\xDA', '\x5', '*', '\x16', '\x2', '\xDA', '\xDB', + '\a', '.', '\x2', '\x2', '\xDB', '\xDC', '\x5', ',', '\x17', '\x2', '\xDC', + ')', '\x3', '\x2', '\x2', '\x2', '\xDD', '\xDE', '\t', '\x2', '\x2', '\x2', + '\xDE', '+', '\x3', '\x2', '\x2', '\x2', '\xDF', '\xE0', '\t', '\x2', + '\x2', '\x2', '\xE0', '-', '\x3', '\x2', '\x2', '\x2', '\xE1', '\xE2', + '\b', '\x18', '\x1', '\x2', '\xE2', '\xED', '\x5', '\x30', '\x19', '\x2', + '\xE3', '\xE5', '\x5', '\x38', '\x1D', '\x2', '\xE4', '\xE6', '\a', '/', + '\x2', '\x2', '\xE5', '\xE4', '\x3', '\x2', '\x2', '\x2', '\xE5', '\xE6', + '\x3', '\x2', '\x2', '\x2', '\xE6', '\xE7', '\x3', '\x2', '\x2', '\x2', + '\xE7', '\xE8', '\a', '\"', '\x2', '\x2', '\xE8', '\xE9', '\x5', '\x38', + '\x1D', '\x2', '\xE9', '\xEA', '\a', '\x1E', '\x2', '\x2', '\xEA', '\xEB', + '\x5', '\x38', '\x1D', '\x2', '\xEB', '\xED', '\x3', '\x2', '\x2', '\x2', + '\xEC', '\xE1', '\x3', '\x2', '\x2', '\x2', '\xEC', '\xE3', '\x3', '\x2', + '\x2', '\x2', '\xED', '\xF9', '\x3', '\x2', '\x2', '\x2', '\xEE', '\xEF', + '\f', '\x6', '\x2', '\x2', '\xEF', '\xF0', '\a', '\n', '\x2', '\x2', '\xF0', + '\xF1', '\x5', '.', '\x18', '\x2', '\xF1', '\xF2', '\a', '\v', '\x2', + '\x2', '\xF2', '\xF3', '\x5', '.', '\x18', '\a', '\xF3', '\xF8', '\x3', + '\x2', '\x2', '\x2', '\xF4', '\xF5', '\f', '\x5', '\x2', '\x2', '\xF5', + '\xF6', '\a', '\f', '\x2', '\x2', '\xF6', '\xF8', '\x5', '.', '\x18', + '\x6', '\xF7', '\xEE', '\x3', '\x2', '\x2', '\x2', '\xF7', '\xF4', '\x3', + '\x2', '\x2', '\x2', '\xF8', '\xFB', '\x3', '\x2', '\x2', '\x2', '\xF9', + '\xF7', '\x3', '\x2', '\x2', '\x2', '\xF9', '\xFA', '\x3', '\x2', '\x2', + '\x2', '\xFA', '/', '\x3', '\x2', '\x2', '\x2', '\xFB', '\xF9', '\x3', + '\x2', '\x2', '\x2', '\xFC', '\xFD', '\b', '\x19', '\x1', '\x2', '\xFD', + '\x101', '\x5', '\x38', '\x1D', '\x2', '\xFE', '\x101', '\x5', '\x32', + '\x1A', '\x2', '\xFF', '\x101', '\x5', '\x34', '\x1B', '\x2', '\x100', + '\xFC', '\x3', '\x2', '\x2', '\x2', '\x100', '\xFE', '\x3', '\x2', '\x2', + '\x2', '\x100', '\xFF', '\x3', '\x2', '\x2', '\x2', '\x101', '\x10A', + '\x3', '\x2', '\x2', '\x2', '\x102', '\x103', '\f', '\x4', '\x2', '\x2', + '\x103', '\x104', '\a', '\x1E', '\x2', '\x2', '\x104', '\x109', '\x5', + '\x30', '\x19', '\x5', '\x105', '\x106', '\f', '\x3', '\x2', '\x2', '\x106', + '\x107', '\a', '\x32', '\x2', '\x2', '\x107', '\x109', '\x5', '\x30', + '\x19', '\x4', '\x108', '\x102', '\x3', '\x2', '\x2', '\x2', '\x108', + '\x105', '\x3', '\x2', '\x2', '\x2', '\x109', '\x10C', '\x3', '\x2', '\x2', + '\x2', '\x10A', '\x108', '\x3', '\x2', '\x2', '\x2', '\x10A', '\x10B', + '\x3', '\x2', '\x2', '\x2', '\x10B', '\x31', '\x3', '\x2', '\x2', '\x2', + '\x10C', '\x10A', '\x3', '\x2', '\x2', '\x2', '\x10D', '\x10F', '\x5', + '\x38', '\x1D', '\x2', '\x10E', '\x110', '\a', '/', '\x2', '\x2', '\x10F', + '\x10E', '\x3', '\x2', '\x2', '\x2', '\x10F', '\x110', '\x3', '\x2', '\x2', + '\x2', '\x110', '\x111', '\x3', '\x2', '\x2', '\x2', '\x111', '\x112', + '\a', '+', '\x2', '\x2', '\x112', '\x113', '\a', '\x5', '\x2', '\x2', + '\x113', '\x114', '\x5', 'P', ')', '\x2', '\x114', '\x115', '\a', '\x6', + '\x2', '\x2', '\x115', '\x33', '\x3', '\x2', '\x2', '\x2', '\x116', '\x118', + '\x5', '\x38', '\x1D', '\x2', '\x117', '\x119', '\a', '/', '\x2', '\x2', + '\x118', '\x117', '\x3', '\x2', '\x2', '\x2', '\x118', '\x119', '\x3', + '\x2', '\x2', '\x2', '\x119', '\x11A', '\x3', '\x2', '\x2', '\x2', '\x11A', + '\x11B', '\a', '-', '\x2', '\x2', '\x11B', '\x11D', '\x5', '\x38', '\x1D', + '\x2', '\x11C', '\x11E', '\x5', '\x36', '\x1C', '\x2', '\x11D', '\x11C', + '\x3', '\x2', '\x2', '\x2', '\x11D', '\x11E', '\x3', '\x2', '\x2', '\x2', + '\x11E', '\x35', '\x3', '\x2', '\x2', '\x2', '\x11F', '\x120', '\a', '&', + '\x2', '\x2', '\x120', '\x121', '\a', '=', '\x2', '\x2', '\x121', '\x37', + '\x3', '\x2', '\x2', '\x2', '\x122', '\x123', '\b', '\x1D', '\x1', '\x2', + '\x123', '\x124', '\x5', 'J', '&', '\x2', '\x124', '\x147', '\x3', '\x2', + '\x2', '\x2', '\x125', '\x126', '\f', '\n', '\x2', '\x2', '\x126', '\x127', + '\x5', ':', '\x1E', '\x2', '\x127', '\x128', '\x5', '\x38', '\x1D', '\v', + '\x128', '\x146', '\x3', '\x2', '\x2', '\x2', '\x129', '\x12A', '\f', + '\t', '\x2', '\x2', '\x12A', '\x12B', '\x5', '<', '\x1F', '\x2', '\x12B', + '\x12C', '\x5', '\x38', '\x1D', '\n', '\x12C', '\x146', '\x3', '\x2', + '\x2', '\x2', '\x12D', '\x12E', '\f', '\b', '\x2', '\x2', '\x12E', '\x12F', + '\x5', '>', ' ', '\x2', '\x12F', '\x130', '\x5', '\x38', '\x1D', '\t', + '\x130', '\x146', '\x3', '\x2', '\x2', '\x2', '\x131', '\x132', '\f', + '\a', '\x2', '\x2', '\x132', '\x133', '\x5', '@', '!', '\x2', '\x133', + '\x134', '\x5', '\x38', '\x1D', '\b', '\x134', '\x146', '\x3', '\x2', + '\x2', '\x2', '\x135', '\x136', '\f', '\x6', '\x2', '\x2', '\x136', '\x137', + '\x5', '\x42', '\"', '\x2', '\x137', '\x138', '\x5', '\x38', '\x1D', '\a', + '\x138', '\x146', '\x3', '\x2', '\x2', '\x2', '\x139', '\x13A', '\f', + '\x5', '\x2', '\x2', '\x13A', '\x13B', '\x5', '\x44', '#', '\x2', '\x13B', + '\x13C', '\x5', '\x38', '\x1D', '\x6', '\x13C', '\x146', '\x3', '\x2', + '\x2', '\x2', '\x13D', '\x13E', '\f', '\x4', '\x2', '\x2', '\x13E', '\x13F', + '\x5', '\x46', '$', '\x2', '\x13F', '\x140', '\x5', '\x38', '\x1D', '\x5', + '\x140', '\x146', '\x3', '\x2', '\x2', '\x2', '\x141', '\x142', '\f', + '\x3', '\x2', '\x2', '\x142', '\x143', '\x5', 'H', '%', '\x2', '\x143', + '\x144', '\x5', '\x38', '\x1D', '\x4', '\x144', '\x146', '\x3', '\x2', + '\x2', '\x2', '\x145', '\x125', '\x3', '\x2', '\x2', '\x2', '\x145', '\x129', + '\x3', '\x2', '\x2', '\x2', '\x145', '\x12D', '\x3', '\x2', '\x2', '\x2', + '\x145', '\x131', '\x3', '\x2', '\x2', '\x2', '\x145', '\x135', '\x3', + '\x2', '\x2', '\x2', '\x145', '\x139', '\x3', '\x2', '\x2', '\x2', '\x145', + '\x13D', '\x3', '\x2', '\x2', '\x2', '\x145', '\x141', '\x3', '\x2', '\x2', + '\x2', '\x146', '\x149', '\x3', '\x2', '\x2', '\x2', '\x147', '\x145', + '\x3', '\x2', '\x2', '\x2', '\x147', '\x148', '\x3', '\x2', '\x2', '\x2', + '\x148', '\x39', '\x3', '\x2', '\x2', '\x2', '\x149', '\x147', '\x3', + '\x2', '\x2', '\x2', '\x14A', '\x14B', '\t', '\x4', '\x2', '\x2', '\x14B', + ';', '\x3', '\x2', '\x2', '\x2', '\x14C', '\x14D', '\t', '\x5', '\x2', + '\x2', '\x14D', '=', '\x3', '\x2', '\x2', '\x2', '\x14E', '\x14F', '\t', + '\x6', '\x2', '\x2', '\x14F', '?', '\x3', '\x2', '\x2', '\x2', '\x150', + '\x151', '\t', '\a', '\x2', '\x2', '\x151', '\x41', '\x3', '\x2', '\x2', + '\x2', '\x152', '\x153', '\a', '\x17', '\x2', '\x2', '\x153', '\x43', + '\x3', '\x2', '\x2', '\x2', '\x154', '\x155', '\a', '\x18', '\x2', '\x2', + '\x155', '\x45', '\x3', '\x2', '\x2', '\x2', '\x156', '\x157', '\a', '\x19', + '\x2', '\x2', '\x157', 'G', '\x3', '\x2', '\x2', '\x2', '\x158', '\x159', + '\a', '\x1A', '\x2', '\x2', '\x159', 'I', '\x3', '\x2', '\x2', '\x2', + '\x15A', '\x15F', '\x5', 'N', '(', '\x2', '\x15B', '\x15C', '\x5', 'L', + '\'', '\x2', '\x15C', '\x15D', '\x5', 'J', '&', '\x2', '\x15D', '\x15F', + '\x3', '\x2', '\x2', '\x2', '\x15E', '\x15A', '\x3', '\x2', '\x2', '\x2', + '\x15E', '\x15B', '\x3', '\x2', '\x2', '\x2', '\x15F', 'K', '\x3', '\x2', + '\x2', '\x2', '\x160', '\x161', '\t', '\b', '\x2', '\x2', '\x161', 'M', + '\x3', '\x2', '\x2', '\x2', '\x162', '\x163', '\b', '(', '\x1', '\x2', + '\x163', '\x18D', '\a', '>', '\x2', '\x2', '\x164', '\x18D', '\a', '?', + '\x2', '\x2', '\x165', '\x18D', '\x5', 'V', ',', '\x2', '\x166', '\x168', + '\a', '\b', '\x2', '\x2', '\x167', '\x169', '\x5', 'P', ')', '\x2', '\x168', + '\x167', '\x3', '\x2', '\x2', '\x2', '\x168', '\x169', '\x3', '\x2', '\x2', + '\x2', '\x169', '\x16A', '\x3', '\x2', '\x2', '\x2', '\x16A', '\x18D', + '\a', '\t', '\x2', '\x2', '\x16B', '\x16D', '\a', '\x1C', '\x2', '\x2', + '\x16C', '\x16E', '\x5', 'R', '*', '\x2', '\x16D', '\x16C', '\x3', '\x2', + '\x2', '\x2', '\x16D', '\x16E', '\x3', '\x2', '\x2', '\x2', '\x16E', '\x16F', + '\x3', '\x2', '\x2', '\x2', '\x16F', '\x18D', '\a', '\x1D', '\x2', '\x2', + '\x170', '\x171', '\a', '\x37', '\x2', '\x2', '\x171', '\x173', '\a', + '\a', '\x2', '\x2', '\x172', '\x170', '\x3', '\x2', '\x2', '\x2', '\x172', + '\x173', '\x3', '\x2', '\x2', '\x2', '\x173', '\x174', '\x3', '\x2', '\x2', + '\x2', '\x174', '\x175', '\a', '>', '\x2', '\x2', '\x175', '\x177', '\a', + '\x5', '\x2', '\x2', '\x176', '\x178', '\x5', 'P', ')', '\x2', '\x177', + '\x176', '\x3', '\x2', '\x2', '\x2', '\x177', '\x178', '\x3', '\x2', '\x2', + '\x2', '\x178', '\x179', '\x3', '\x2', '\x2', '\x2', '\x179', '\x18D', + '\a', '\x6', '\x2', '\x2', '\x17A', '\x17B', '\a', '\x5', '\x2', '\x2', + '\x17B', '\x17C', '\x5', '.', '\x18', '\x2', '\x17C', '\x17D', '\a', '\x6', + '\x2', '\x2', '\x17D', '\x18D', '\x3', '\x2', '\x2', '\x2', '\x17E', '\x17F', + '\a', '\x5', '\x2', '\x2', '\x17F', '\x180', '\x5', '\x4', '\x3', '\x2', + '\x180', '\x181', '\a', '\x6', '\x2', '\x2', '\x181', '\x18D', '\x3', + '\x2', '\x2', '\x2', '\x182', '\x183', '\a', '\'', '\x2', '\x2', '\x183', + '\x184', '\a', '\x5', '\x2', '\x2', '\x184', '\x185', '\x5', '\x4', '\x3', + '\x2', '\x185', '\x186', '\a', '\x6', '\x2', '\x2', '\x186', '\x18D', + '\x3', '\x2', '\x2', '\x2', '\x187', '\x188', '\a', '\x1F', '\x2', '\x2', + '\x188', '\x189', '\a', '\x5', '\x2', '\x2', '\x189', '\x18A', '\x5', + '\x4', '\x3', '\x2', '\x18A', '\x18B', '\a', '\x6', '\x2', '\x2', '\x18B', + '\x18D', '\x3', '\x2', '\x2', '\x2', '\x18C', '\x162', '\x3', '\x2', '\x2', + '\x2', '\x18C', '\x164', '\x3', '\x2', '\x2', '\x2', '\x18C', '\x165', + '\x3', '\x2', '\x2', '\x2', '\x18C', '\x166', '\x3', '\x2', '\x2', '\x2', + '\x18C', '\x16B', '\x3', '\x2', '\x2', '\x2', '\x18C', '\x172', '\x3', + '\x2', '\x2', '\x2', '\x18C', '\x17A', '\x3', '\x2', '\x2', '\x2', '\x18C', + '\x17E', '\x3', '\x2', '\x2', '\x2', '\x18C', '\x182', '\x3', '\x2', '\x2', + '\x2', '\x18C', '\x187', '\x3', '\x2', '\x2', '\x2', '\x18D', '\x198', + '\x3', '\x2', '\x2', '\x2', '\x18E', '\x18F', '\f', '\x6', '\x2', '\x2', + '\x18F', '\x190', '\a', '\a', '\x2', '\x2', '\x190', '\x197', '\a', '>', + '\x2', '\x2', '\x191', '\x192', '\f', '\x5', '\x2', '\x2', '\x192', '\x193', + '\a', '\b', '\x2', '\x2', '\x193', '\x194', '\x5', '.', '\x18', '\x2', + '\x194', '\x195', '\a', '\t', '\x2', '\x2', '\x195', '\x197', '\x3', '\x2', + '\x2', '\x2', '\x196', '\x18E', '\x3', '\x2', '\x2', '\x2', '\x196', '\x191', + '\x3', '\x2', '\x2', '\x2', '\x197', '\x19A', '\x3', '\x2', '\x2', '\x2', + '\x198', '\x196', '\x3', '\x2', '\x2', '\x2', '\x198', '\x199', '\x3', + '\x2', '\x2', '\x2', '\x199', 'O', '\x3', '\x2', '\x2', '\x2', '\x19A', + '\x198', '\x3', '\x2', '\x2', '\x2', '\x19B', '\x1A0', '\x5', '.', '\x18', + '\x2', '\x19C', '\x19D', '\a', '\x4', '\x2', '\x2', '\x19D', '\x19F', + '\x5', '.', '\x18', '\x2', '\x19E', '\x19C', '\x3', '\x2', '\x2', '\x2', + '\x19F', '\x1A2', '\x3', '\x2', '\x2', '\x2', '\x1A0', '\x19E', '\x3', + '\x2', '\x2', '\x2', '\x1A0', '\x1A1', '\x3', '\x2', '\x2', '\x2', '\x1A1', + 'Q', '\x3', '\x2', '\x2', '\x2', '\x1A2', '\x1A0', '\x3', '\x2', '\x2', + '\x2', '\x1A3', '\x1A8', '\x5', 'T', '+', '\x2', '\x1A4', '\x1A5', '\a', + '\x4', '\x2', '\x2', '\x1A5', '\x1A7', '\x5', 'T', '+', '\x2', '\x1A6', + '\x1A4', '\x3', '\x2', '\x2', '\x2', '\x1A7', '\x1AA', '\x3', '\x2', '\x2', + '\x2', '\x1A8', '\x1A6', '\x3', '\x2', '\x2', '\x2', '\x1A8', '\x1A9', + '\x3', '\x2', '\x2', '\x2', '\x1A9', 'S', '\x3', '\x2', '\x2', '\x2', + '\x1AA', '\x1A8', '\x3', '\x2', '\x2', '\x2', '\x1AB', '\x1AC', '\a', + '=', '\x2', '\x2', '\x1AC', '\x1AD', '\a', '\v', '\x2', '\x2', '\x1AD', + '\x1AE', '\x5', '.', '\x18', '\x2', '\x1AE', 'U', '\x3', '\x2', '\x2', + '\x2', '\x1AF', '\x1B0', '\t', '\t', '\x2', '\x2', '\x1B0', 'W', '\x3', + '\x2', '\x2', '\x2', '+', ']', '`', '\x63', '\x66', 'i', 'm', 'p', 'z', '\x86', '\x8C', '\x95', '\x9A', '\xA1', '\xA6', '\xAC', '\xBA', '\xBC', - '\xCF', '\xD4', '\xE5', '\xEC', '\xF7', '\xF9', '\xFF', '\x103', '\x10C', - '\x111', '\x13F', '\x141', '\x158', '\x162', '\x167', '\x16C', '\x171', - '\x186', '\x190', '\x192', '\x19A', '\x1A2', + '\xCF', '\xD4', '\xE5', '\xEC', '\xF7', '\xF9', '\x100', '\x108', '\x10A', + '\x10F', '\x118', '\x11D', '\x145', '\x147', '\x15E', '\x168', '\x16D', + '\x172', '\x177', '\x18C', '\x196', '\x198', '\x1A0', '\x1A8', }; public static readonly ATN _ATN = diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/OrderBy/OrderByCrossPartitionQueryPipelineStage.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/OrderBy/OrderByCrossPartitionQueryPipelineStage.cs index ad2b49db64..1190ca9892 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/OrderBy/OrderByCrossPartitionQueryPipelineStage.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Pipeline/CrossPartition/OrderBy/OrderByCrossPartitionQueryPipelineStage.cs @@ -1166,7 +1166,8 @@ private sealed class CosmosElementToIsSystemFunctionsVisitor : ICosmosElementVis private static class IsSystemFunctions { - public const string Undefined = "not IS_DEFINED"; + public const string Defined = "IS_DEFINED"; + public const string Undefined = "NOT IS_DEFINED"; public const string Null = "IS_NULL"; public const string Boolean = "IS_BOOLEAN"; public const string Number = "IS_NUMBER"; @@ -1186,6 +1187,12 @@ private static class IsSystemFunctions IsSystemFunctions.Object, }; + private static readonly ReadOnlyMemory ExtendedTypesSystemFunctionSortOrder = new string[] + { + IsSystemFunctions.Undefined, + IsSystemFunctions.Defined + }; + private static class SortOrder { public const int Undefined = 0; @@ -1197,6 +1204,12 @@ private static class SortOrder public const int Object = 6; } + private static class ExtendedTypesSortOrder + { + public const int Undefined = 0; + public const int Defined = 1; + } + private CosmosElementToIsSystemFunctionsVisitor() { } @@ -1208,7 +1221,7 @@ public ReadOnlyMemory Visit(CosmosArray cosmosArray, bool isAscending) public ReadOnlyMemory Visit(CosmosBinary cosmosBinary, bool isAscending) { - throw new NotImplementedException(); + return GetExtendedTypesIsDefinedFunctions(ExtendedTypesSortOrder.Defined, isAscending); } public ReadOnlyMemory Visit(CosmosBoolean cosmosBoolean, bool isAscending) @@ -1218,7 +1231,7 @@ public ReadOnlyMemory Visit(CosmosBoolean cosmosBoolean, bool isAscendin public ReadOnlyMemory Visit(CosmosGuid cosmosGuid, bool isAscending) { - throw new NotImplementedException(); + return GetExtendedTypesIsDefinedFunctions(ExtendedTypesSortOrder.Defined, isAscending); } public ReadOnlyMemory Visit(CosmosNull cosmosNull, bool isAscending) @@ -1250,6 +1263,13 @@ private static ReadOnlyMemory GetIsDefinedFunctions(int index, bool isAs { return isAscending ? SystemFunctionSortOrder.Slice(index + 1) : SystemFunctionSortOrder.Slice(start: 0, index); } + + private static ReadOnlyMemory GetExtendedTypesIsDefinedFunctions(int index, bool isAscending) + { + return isAscending ? + ExtendedTypesSystemFunctionSortOrder.Slice(index + 1) : + ExtendedTypesSystemFunctionSortOrder.Slice(start: 0, index); + } } private readonly struct ComparisionWithUndefinedFilters diff --git a/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextFactory.cs b/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextFactory.cs index 3501470f83..63f17406c5 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v2Query/DocumentQueryExecutionContextFactory.cs @@ -46,7 +46,7 @@ public static async Task CreateDocumentQueryExec collection = await collectionCache.ResolveCollectionAsync(request, token, NoOpTrace.Singleton); } - if (feedOptions != null && feedOptions.PartitionKey != null && feedOptions.PartitionKey.Equals(Documents.PartitionKey.None)) + if (feedOptions?.PartitionKey != null && feedOptions.PartitionKey.Equals(Documents.PartitionKey.None)) { feedOptions.PartitionKey = Documents.PartitionKey.FromInternalKey(collection.GetNoneValue()); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index ce43da78ce..08ac35b797 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -286,6 +286,7 @@ internal override Task ProcessResourceOperationStreamAsync( partitionKey: partitionKey.Value, itemId: itemId, streamPayload: streamPayload, + trace: trace, cancellationToken: cancellationToken); } @@ -432,6 +433,19 @@ private async Task RunWithDiagnosticsHelperAsync( { throw new CosmosOperationCanceledException(oe, trace); } + catch (ObjectDisposedException objectDisposed) when (!(objectDisposed is CosmosObjectDisposedException)) + { + throw new CosmosObjectDisposedException( + objectDisposed, + this.client, + trace); + } + catch (NullReferenceException nullRefException) when (!(nullRefException is CosmosNullReferenceException)) + { + throw new CosmosNullReferenceException( + nullRefException, + trace); + } } } @@ -442,6 +456,7 @@ private async Task ProcessResourceOperationAsBulkStreamAsync( PartitionKey partitionKey, string itemId, Stream streamPayload, + ITrace trace, CancellationToken cancellationToken) { this.ThrowIfDisposed(); @@ -458,6 +473,7 @@ private async Task ProcessResourceOperationAsBulkStreamAsync( TransactionalBatchOperationResult batchOperationResult = await cosmosContainerCore.BatchExecutor.AddAsync( itemBatchOperation, + trace, itemRequestOptions, cancellationToken); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs new file mode 100644 index 0000000000..ad4e8cb266 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNullReferenceException.cs @@ -0,0 +1,78 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing; + + /// + /// The exception is a wrapper for NullReferenceExceptions. This wrapper + /// adds a way to access the CosmosDiagnostics and appends additional information + /// to the message for easier troubleshooting. + /// + internal class CosmosNullReferenceException : NullReferenceException + { + private readonly NullReferenceException originalException; + + /// + /// Create an instance of CosmosNullReferenceException + /// + internal CosmosNullReferenceException( + NullReferenceException originalException, + ITrace trace) + { + this.originalException = originalException ?? throw new ArgumentNullException(nameof(originalException)); + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + this.Diagnostics = new CosmosTraceDiagnostics(trace); + } + + /// + public override string Source + { + get => this.originalException.Source; + set => this.originalException.Source = value; + } + + /// + public override string Message => this.originalException.Message + this.Diagnostics.ToString(); + + /// + public override string StackTrace => this.originalException.StackTrace; + + /// + public override IDictionary Data => this.originalException.Data; + + /// + /// Gets the diagnostics for the request + /// + public CosmosDiagnostics Diagnostics { get; } + + /// + public override string HelpLink + { + get => this.originalException.HelpLink; + set => this.originalException.HelpLink = value; + } + + /// + public override Exception GetBaseException() + { + return this.originalException.GetBaseException(); + } + + /// + public override string ToString() + { + return $"{this.originalException} {Environment.NewLine} CosmosDiagnostics: {this.Diagnostics}"; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs new file mode 100644 index 0000000000..89925310e5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosObjectDisposedException.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + using System; + using System.Collections; + using System.Globalization; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Tracing; + + /// + /// The exception is a wrapper for ObjectDisposedExceptions. This wrapper + /// adds a way to access the CosmosDiagnostics and appends additional information + /// to the message for easier troubleshooting. + /// + internal class CosmosObjectDisposedException : ObjectDisposedException + { + private readonly ObjectDisposedException originalException; + private readonly CosmosClient cosmosClient; + + /// + /// Create an instance of CosmosObjectDisposedException + /// + internal CosmosObjectDisposedException( + ObjectDisposedException originalException, + CosmosClient cosmosClient, + ITrace trace) + : base(originalException.ObjectName) + { + this.cosmosClient = cosmosClient ?? throw new ArgumentNullException(nameof(CosmosClient)); + this.originalException = originalException ?? throw new ArgumentNullException(nameof(originalException)); + + string additionalInfo = $"CosmosClient Endpoint: {this.cosmosClient.Endpoint}; Created at: {this.cosmosClient.ClientConfigurationTraceDatum.ClientCreatedDateTimeUtc.ToString("o", CultureInfo.InvariantCulture)};" + + $" UserAgent: {this.cosmosClient.ClientConfigurationTraceDatum.UserAgentContainer.UserAgent};"; + this.Message = this.cosmosClient.DisposedDateTimeUtc.HasValue + ? $"Cannot access a disposed 'CosmosClient'. Follow best practices and use the CosmosClient as a singleton." + + $" CosmosClient was disposed at: {this.cosmosClient.DisposedDateTimeUtc.Value.ToString("o", CultureInfo.InvariantCulture)}; {additionalInfo}" + : $"{originalException.Message} The CosmosClient is still active and NOT disposed of. {additionalInfo}"; + + if (trace == null) + { + throw new ArgumentNullException(nameof(trace)); + } + + this.Diagnostics = new CosmosTraceDiagnostics(trace); + } + + /// + public override string Source + { + get => this.originalException.Source; + set => this.originalException.Source = value; + } + + /// + public override string Message { get; } + + /// + public override string StackTrace => this.originalException.StackTrace; + + /// + public override IDictionary Data => this.originalException.Data; + + /// + /// Gets the diagnostics for the request + /// + public CosmosDiagnostics Diagnostics { get; } + + /// + public override string HelpLink + { + get => this.originalException.HelpLink; + set => this.originalException.HelpLink = value; + } + + /// + public override Exception GetBaseException() + { + return this.originalException.GetBaseException(); + } + + /// + public override string ToString() + { + return $"{this.Message} {Environment.NewLine}CosmosDiagnostics: {this.Diagnostics} StackTrace: {this.StackTrace}"; + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/Permission/Permission.cs b/Microsoft.Azure.Cosmos/src/Resource/Permission/Permission.cs index 3acf255384..fbdb2da137 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Permission/Permission.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Permission/Permission.cs @@ -43,7 +43,7 @@ public abstract class Permission /// /// Reads a from the Azure Cosmos service as an asynchronous operation. Each read will return a new ResourceToken with its respective expiration. /// - /// (Optional) The expiry time for resource token in seconds. This value can range from 10 seconds, to 24 hours (or 86,400 seconds). The default value for this is 1 hour (or 3,600 seconds). This does not change the default value for future tokens. + /// (Optional) The expiry time for resource token in seconds. This value can range from 10 minutes (or 600 seconds), to 24 hours (or 86,400 seconds). The default value for this is 1 hour (or 3,600 seconds). This does not change the default value for future tokens. /// (Optional) The options for the permission request. /// (Optional) representing request cancellation. /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs index 4708acc410..a1e3348f07 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ClientEncryptionPolicy.cs @@ -6,6 +6,8 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; using Newtonsoft.Json; /// @@ -24,11 +26,16 @@ sealed class ClientEncryptionPolicy /// List of paths to include in the policy definition. public ClientEncryptionPolicy(IEnumerable includedPaths) { - this.ValidateIncludedPaths(includedPaths); + ClientEncryptionPolicy.ValidateIncludedPaths(includedPaths); this.IncludedPaths = includedPaths; this.PolicyFormatVersion = 1; } + [JsonConstructor] + private ClientEncryptionPolicy() + { + } + /// /// Paths of the item that need encryption along with path-specific settings. /// @@ -38,15 +45,40 @@ public IEnumerable IncludedPaths get; private set; } + /// + /// Version of the client encryption policy definition. + /// [JsonProperty(PropertyName = "policyFormatVersion")] - internal int PolicyFormatVersion { get; set; } + public int PolicyFormatVersion { get; private set; } + + /// + /// Ensures that partition key paths are not specified in the client encryption policy for encryption. + /// + /// Tokens corresponding to validated partition key. + internal void ValidatePartitionKeyPathsAreNotEncrypted(IReadOnlyList> partitionKeyPathTokens) + { + Debug.Assert(partitionKeyPathTokens != null); + IEnumerable propertiesToEncrypt = this.IncludedPaths.Select(p => p.Path.Substring(1)); + foreach (IReadOnlyList tokensInPath in partitionKeyPathTokens) + { + Debug.Assert(tokensInPath != null); + if (tokensInPath.Count > 0) + { + string topLevelToken = tokensInPath.First(); + if (propertiesToEncrypt.Contains(topLevelToken)) + { + throw new ArgumentException($"Paths which are part of the partition key may not be included in the {nameof(ClientEncryptionPolicy)}.", nameof(ContainerProperties.ClientEncryptionPolicy)); + } + } + } + } - private void ValidateIncludedPaths(IEnumerable clientEncryptionIncludedPath) + private static void ValidateIncludedPaths(IEnumerable clientEncryptionIncludedPath) { List includedPathsList = new List(); foreach (ClientEncryptionIncludedPath path in clientEncryptionIncludedPath) { - this.ValidateClientEncryptionIncludedPath(path); + ClientEncryptionPolicy.ValidateClientEncryptionIncludedPath(path); if (includedPathsList.Contains(path.Path)) { throw new ArgumentException("Duplicate Path found.", nameof(clientEncryptionIncludedPath)); @@ -56,7 +88,7 @@ private void ValidateIncludedPaths(IEnumerable cli } } - private void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath) + private static void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath clientEncryptionIncludedPath) { if (clientEncryptionIncludedPath == null) { @@ -86,10 +118,9 @@ private void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath c } if (!string.Equals(clientEncryptionIncludedPath.EncryptionType, "Deterministic") && - !string.Equals(clientEncryptionIncludedPath.EncryptionType, "Randomized") && - !string.Equals(clientEncryptionIncludedPath.EncryptionType, "Plaintext")) + !string.Equals(clientEncryptionIncludedPath.EncryptionType, "Randomized")) { - throw new ArgumentException("EncryptionType should be either 'Deterministic' or 'Randomized' or 'Plaintext'.", nameof(clientEncryptionIncludedPath)); + throw new ArgumentException("EncryptionType should be either 'Deterministic' or 'Randomized'. ", nameof(clientEncryptionIncludedPath)); } if (string.IsNullOrWhiteSpace(clientEncryptionIncludedPath.EncryptionAlgorithm)) @@ -99,7 +130,7 @@ private void ValidateClientEncryptionIncludedPath(ClientEncryptionIncludedPath c if (!string.Equals(clientEncryptionIncludedPath.EncryptionAlgorithm, "AEAD_AES_256_CBC_HMAC_SHA256")) { - throw new ArgumentException("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'.", nameof(clientEncryptionIncludedPath)); + throw new ArgumentException("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'. ", nameof(clientEncryptionIncludedPath)); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs index 8e1bf9af62..5bea80b7ad 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/ContainerProperties.cs @@ -690,6 +690,11 @@ internal void ValidateRequiredProperties() { this.indexingPolicyInternal.IncludedPaths.Add(new IncludedPath() { Path = IndexingPolicy.DefaultPath }); } + + if (this.ClientEncryptionPolicy != null) + { + this.ClientEncryptionPolicy.ValidatePartitionKeyPathsAreNotEncrypted(this.PartitionKeyPathTokens); + } } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Settings/DedicatedGatewayRequestOptions.cs b/Microsoft.Azure.Cosmos/src/Resource/Settings/DedicatedGatewayRequestOptions.cs index 79abc17113..34f87a2e76 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Settings/DedicatedGatewayRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Settings/DedicatedGatewayRequestOptions.cs @@ -21,9 +21,10 @@ class DedicatedGatewayRequestOptions /// /// Gets or sets the staleness value associated with the request in the Azure CosmosDB service. /// + /// Default value is null. /// /// For requests where the is , responses from the integrated cache are guaranteed to be no staler than value indicated by this MaxIntegratedCacheStaleness. - /// Value defaults to null. + /// Cache Staleness is supported in milliseconds granularity. Anything smaller than milliseconds will be ignored. /// public TimeSpan? MaxIntegratedCacheStaleness { get; set; } @@ -31,7 +32,14 @@ internal static void PopulateMaxIntegratedCacheStalenessOption(DedicatedGatewayR { if (dedicatedGatewayRequestOptions?.MaxIntegratedCacheStaleness != null) { - request.Headers.Set(HttpConstants.HttpHeaders.DedicatedGatewayPerRequestCacheStaleness, dedicatedGatewayRequestOptions.MaxIntegratedCacheStaleness.Value.ToString("c", CultureInfo.InvariantCulture)); + double cacheStalenessInMilliseconds = (double)dedicatedGatewayRequestOptions.MaxIntegratedCacheStaleness.Value.TotalMilliseconds; + + if (cacheStalenessInMilliseconds < 0) + { + throw new ArgumentOutOfRangeException(nameof(DedicatedGatewayRequestOptions.MaxIntegratedCacheStaleness), "MaxIntegratedCacheStaleness cannot be negative."); + } + + request.Headers.Set(HttpConstants.HttpHeaders.DedicatedGatewayPerRequestCacheStaleness, cacheStalenessInMilliseconds.ToString(CultureInfo.InvariantCulture)); } } } diff --git a/Microsoft.Azure.Cosmos/src/ResourceThrottleRetryPolicy.cs b/Microsoft.Azure.Cosmos/src/ResourceThrottleRetryPolicy.cs index d8d1d4c885..c6187f3e51 100644 --- a/Microsoft.Azure.Cosmos/src/ResourceThrottleRetryPolicy.cs +++ b/Microsoft.Azure.Cosmos/src/ResourceThrottleRetryPolicy.cs @@ -48,9 +48,8 @@ public Task ShouldRetryAsync( Exception exception, CancellationToken cancellationToken) { - if (exception is DocumentClientException) + if (exception is DocumentClientException dce) { - DocumentClientException dce = (DocumentClientException)exception; if (!this.IsValidThrottleStatusCode(dce.StatusCode)) { DefaultTrace.TraceError( diff --git a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs index 90ef684db0..de9b393de6 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/ClientCollectionCache.cs @@ -19,18 +19,13 @@ namespace Microsoft.Azure.Cosmos.Routing internal class ClientCollectionCache : CollectionCache { private readonly IStoreModel storeModel; - private readonly IAuthorizationTokenProvider tokenProvider; + private readonly ICosmosAuthorizationTokenProvider tokenProvider; private readonly IRetryPolicyFactory retryPolicy; private readonly ISessionContainer sessionContainer; - public ClientCollectionCache(ISessionContainer sessionContainer, IStoreModel storeModel, IAuthorizationTokenProvider tokenProvider, IRetryPolicyFactory retryPolicy) + public ClientCollectionCache(ISessionContainer sessionContainer, IStoreModel storeModel, ICosmosAuthorizationTokenProvider tokenProvider, IRetryPolicyFactory retryPolicy) { - if (storeModel == null) - { - throw new ArgumentNullException("storeModel"); - } - - this.storeModel = storeModel; + this.storeModel = storeModel ?? throw new ArgumentNullException("storeModel"); this.tokenProvider = tokenProvider; this.retryPolicy = retryPolicy; this.sessionContainer = sessionContainer; @@ -89,21 +84,19 @@ private async Task ReadCollectionAsync(string collectionLin childTrace.AddDatum("Client Side Request Stats", request.RequestContext.ClientRequestStatistics); } - (string authorizationToken, string payload) = await this.tokenProvider.GetUserAuthorizationAsync( + string authorizationToken = await this.tokenProvider.GetUserAuthorizationTokenAsync( request.ResourceAddress, PathsHelper.GetResourcePath(request.ResourceType), HttpConstants.HttpMethods.Get, request.Headers, - AuthorizationTokenType.PrimaryMasterKey); + AuthorizationTokenType.PrimaryMasterKey, + childTrace); request.Headers[HttpConstants.HttpHeaders.Authorization] = authorizationToken; using (new ActivityScope(Guid.NewGuid())) { - if (retryPolicyInstance != null) - { - retryPolicyInstance.OnBeforeSendRequest(request); - } + retryPolicyInstance?.OnBeforeSendRequest(request); try { diff --git a/Microsoft.Azure.Cosmos/src/Routing/CollectionCache.cs b/Microsoft.Azure.Cosmos/src/Routing/CollectionCache.cs index 750b376302..32a83ffed7 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/CollectionCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/CollectionCache.cs @@ -220,7 +220,7 @@ private async Task ResolveByPartitionKeyRangeIdentityAsync( { // if request is targeted at specific partition using x-ms-documentd-partitionkeyrangeid header, // which contains value ",", then resolve to collection rid in this header. - if (partitionKeyRangeIdentity != null && partitionKeyRangeIdentity.CollectionRid != null) + if (partitionKeyRangeIdentity?.CollectionRid != null) { try { diff --git a/Microsoft.Azure.Cosmos/src/Routing/GatewayAddressCache.cs b/Microsoft.Azure.Cosmos/src/Routing/GatewayAddressCache.cs index 03731f340c..f9f541b12b 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/GatewayAddressCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/GatewayAddressCache.cs @@ -41,10 +41,10 @@ internal class GatewayAddressCache : IAddressCache private readonly Protocol protocol; private readonly string protocolFilter; - private readonly IAuthorizationTokenProvider tokenProvider; + private readonly ICosmosAuthorizationTokenProvider tokenProvider; private readonly bool enableTcpConnectionEndpointRediscovery; - private CosmosHttpClient httpClient; + private readonly CosmosHttpClient httpClient; private Tuple masterPartitionAddressCache; private DateTime suboptimalMasterPartitionTimestamp; @@ -52,7 +52,7 @@ internal class GatewayAddressCache : IAddressCache public GatewayAddressCache( Uri serviceEndpoint, Protocol protocol, - IAuthorizationTokenProvider tokenProvider, + ICosmosAuthorizationTokenProvider tokenProvider, IServiceConfigurationReader serviceConfigReader, CosmosHttpClient httpClient, long suboptimalPartitionForceRefreshIntervalInSeconds = 600, @@ -432,29 +432,33 @@ private async Task GetMasterAddressesViaGatewayAsync( string resourceTypeToSign = PathsHelper.GetResourcePath(resourceType); headers.Set(HttpConstants.HttpHeaders.XDate, DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)); - (string token, string _) = await this.tokenProvider.GetUserAuthorizationAsync( - resourceAddress, - resourceTypeToSign, - HttpConstants.HttpMethods.Get, - headers, - AuthorizationTokenType.PrimaryMasterKey); - - headers.Set(HttpConstants.HttpHeaders.Authorization, token); - - Uri targetEndpoint = UrlUtility.SetQuery(this.addressEndpoint, UrlUtility.CreateQuery(addressQuery)); - - string identifier = GatewayAddressCache.LogAddressResolutionStart(request, targetEndpoint); - using (HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync( - uri: targetEndpoint, - additionalHeaders: headers, - resourceType: resourceType, - timeoutPolicy: HttpTimeoutPolicyControlPlaneRetriableHotPath.Instance, - clientSideRequestStatistics: request.RequestContext?.ClientRequestStatistics, - cancellationToken: default)) + using (ITrace trace = Trace.GetRootTrace(nameof(GetMasterAddressesViaGatewayAsync), TraceComponent.Authorization, TraceLevel.Info)) { - DocumentServiceResponse documentServiceResponse = await ClientExtensions.ParseResponseAsync(httpResponseMessage); - GatewayAddressCache.LogAddressResolutionEnd(request, identifier); - return documentServiceResponse; + string token = await this.tokenProvider.GetUserAuthorizationTokenAsync( + resourceAddress, + resourceTypeToSign, + HttpConstants.HttpMethods.Get, + headers, + AuthorizationTokenType.PrimaryMasterKey, + trace); + + headers.Set(HttpConstants.HttpHeaders.Authorization, token); + + Uri targetEndpoint = UrlUtility.SetQuery(this.addressEndpoint, UrlUtility.CreateQuery(addressQuery)); + + string identifier = GatewayAddressCache.LogAddressResolutionStart(request, targetEndpoint); + using (HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync( + uri: targetEndpoint, + additionalHeaders: headers, + resourceType: resourceType, + timeoutPolicy: HttpTimeoutPolicyControlPlaneRetriableHotPath.Instance, + clientSideRequestStatistics: request.RequestContext?.ClientRequestStatistics, + cancellationToken: default)) + { + DocumentServiceResponse documentServiceResponse = await ClientExtensions.ParseResponseAsync(httpResponseMessage); + GatewayAddressCache.LogAddressResolutionEnd(request, identifier); + return documentServiceResponse; + } } } @@ -489,47 +493,53 @@ private async Task GetServerAddressesViaGatewayAsync( headers.Set(HttpConstants.HttpHeaders.XDate, DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)); string token = null; - try - { - token = (await this.tokenProvider.GetUserAuthorizationAsync( - collectionRid, - resourceTypeToSign, - HttpConstants.HttpMethods.Get, - headers, - AuthorizationTokenType.PrimaryMasterKey)).token; - } - catch (UnauthorizedException) - { - } - if (token == null && request != null && request.IsNameBased) + using (ITrace trace = Trace.GetRootTrace(nameof(GetMasterAddressesViaGatewayAsync), TraceComponent.Authorization, TraceLevel.Info)) { - // User doesn't have rid based resource token. Maybe he has name based. - string collectionAltLink = PathsHelper.GetCollectionPath(request.ResourceAddress); - token = (await this.tokenProvider.GetUserAuthorizationAsync( - collectionAltLink, + try + { + token = await this.tokenProvider.GetUserAuthorizationTokenAsync( + collectionRid, resourceTypeToSign, HttpConstants.HttpMethods.Get, headers, - AuthorizationTokenType.PrimaryMasterKey)).token; - } + AuthorizationTokenType.PrimaryMasterKey, + trace); + } + catch (UnauthorizedException) + { + } - headers.Set(HttpConstants.HttpHeaders.Authorization, token); + if (token == null && request != null && request.IsNameBased) + { + // User doesn't have rid based resource token. Maybe he has name based. + string collectionAltLink = PathsHelper.GetCollectionPath(request.ResourceAddress); + token = await this.tokenProvider.GetUserAuthorizationTokenAsync( + collectionAltLink, + resourceTypeToSign, + HttpConstants.HttpMethods.Get, + headers, + AuthorizationTokenType.PrimaryMasterKey, + trace); + } - Uri targetEndpoint = UrlUtility.SetQuery(this.addressEndpoint, UrlUtility.CreateQuery(addressQuery)); + headers.Set(HttpConstants.HttpHeaders.Authorization, token); - string identifier = GatewayAddressCache.LogAddressResolutionStart(request, targetEndpoint); - using (HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync( - uri: targetEndpoint, - additionalHeaders: headers, - resourceType: ResourceType.Document, - timeoutPolicy: HttpTimeoutPolicyControlPlaneRetriableHotPath.Instance, - clientSideRequestStatistics: request.RequestContext?.ClientRequestStatistics, - cancellationToken: default)) - { - DocumentServiceResponse documentServiceResponse = await ClientExtensions.ParseResponseAsync(httpResponseMessage); - GatewayAddressCache.LogAddressResolutionEnd(request, identifier); - return documentServiceResponse; + Uri targetEndpoint = UrlUtility.SetQuery(this.addressEndpoint, UrlUtility.CreateQuery(addressQuery)); + + string identifier = GatewayAddressCache.LogAddressResolutionStart(request, targetEndpoint); + using (HttpResponseMessage httpResponseMessage = await this.httpClient.GetAsync( + uri: targetEndpoint, + additionalHeaders: headers, + resourceType: ResourceType.Document, + timeoutPolicy: HttpTimeoutPolicyControlPlaneRetriableHotPath.Instance, + clientSideRequestStatistics: request.RequestContext?.ClientRequestStatistics, + cancellationToken: default)) + { + DocumentServiceResponse documentServiceResponse = await ClientExtensions.ParseResponseAsync(httpResponseMessage); + GatewayAddressCache.LogAddressResolutionEnd(request, identifier); + return documentServiceResponse; + } } } diff --git a/Microsoft.Azure.Cosmos/src/Routing/GlobalAddressResolver.cs b/Microsoft.Azure.Cosmos/src/Routing/GlobalAddressResolver.cs index c3c10204e5..7f134ac627 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/GlobalAddressResolver.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/GlobalAddressResolver.cs @@ -27,7 +27,7 @@ internal sealed class GlobalAddressResolver : IAddressResolver private readonly GlobalEndpointManager endpointManager; private readonly GlobalPartitionEndpointManager partitionKeyRangeLocationCache; private readonly Protocol protocol; - private readonly IAuthorizationTokenProvider tokenProvider; + private readonly ICosmosAuthorizationTokenProvider tokenProvider; private readonly CollectionCache collectionCache; private readonly PartitionKeyRangeCache routingMapProvider; private readonly int maxEndpoints; @@ -40,7 +40,7 @@ public GlobalAddressResolver( GlobalEndpointManager endpointManager, GlobalPartitionEndpointManager partitionKeyRangeLocationCache, Protocol protocol, - IAuthorizationTokenProvider tokenProvider, + ICosmosAuthorizationTokenProvider tokenProvider, CollectionCache collectionCache, PartitionKeyRangeCache routingMapProvider, IServiceConfigurationReader serviceConfigReader, diff --git a/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs b/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs index 489a8ff5a1..4b0c2da9fb 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/GlobalEndpointManager.cs @@ -27,15 +27,20 @@ internal class GlobalEndpointManager : IGlobalEndpointManager private const int DefaultBackgroundRefreshLocationTimeIntervalInMS = 5 * 60 * 1000; private const string BackgroundRefreshLocationTimeIntervalInMS = "BackgroundRefreshLocationTimeIntervalInMS"; + private const string MinimumIntervalForNonForceRefreshLocationInMS = "MinimumIntervalForNonForceRefreshLocationInMS"; private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private readonly LocationCache locationCache; private readonly Uri defaultEndpoint; private readonly ConnectionPolicy connectionPolicy; private readonly IDocumentClientInternal owner; - private readonly object refreshLock; - private readonly AsyncCache databaseAccountCache; + private readonly AsyncCache databaseAccountCache = new AsyncCache(); + private readonly TimeSpan MinTimeBetweenAccountRefresh = TimeSpan.FromSeconds(15); private readonly int backgroundRefreshLocationTimeIntervalInMS = GlobalEndpointManager.DefaultBackgroundRefreshLocationTimeIntervalInMS; - private bool isRefreshing; + private readonly object backgroundAccountRefreshLock = new object(); + private readonly object isAccountRefreshInProgressLock = new object(); + private bool isAccountRefreshInProgress = false; + private bool isBackgroundAccountRefreshActive = false; + private DateTime LastBackgroundRefreshUtc = DateTime.MinValue; public GlobalEndpointManager(IDocumentClientInternal owner, ConnectionPolicy connectionPolicy) { @@ -49,12 +54,9 @@ public GlobalEndpointManager(IDocumentClientInternal owner, ConnectionPolicy con this.owner = owner; this.defaultEndpoint = owner.ServiceEndpoint; this.connectionPolicy = connectionPolicy; - this.databaseAccountCache = new AsyncCache(); this.connectionPolicy.PreferenceChanged += this.OnPreferenceChanged; - this.isRefreshing = false; - this.refreshLock = new object(); #if !(NETSTANDARD15 || NETSTANDARD16) #if NETSTANDARD20 // GetEntryAssembly returns null when loaded from native netstandard2.0 @@ -73,6 +75,18 @@ public GlobalEndpointManager(IDocumentClientInternal owner, ConnectionPolicy con } #endif #endif + string minimumIntervalForNonForceRefreshLocationInMSConfig = Environment.GetEnvironmentVariable(GlobalEndpointManager.MinimumIntervalForNonForceRefreshLocationInMS); + if (!string.IsNullOrEmpty(minimumIntervalForNonForceRefreshLocationInMSConfig)) + { + if (int.TryParse(minimumIntervalForNonForceRefreshLocationInMSConfig, out int minimumIntervalForNonForceRefreshLocationInMS)) + { + this.MinTimeBetweenAccountRefresh = TimeSpan.FromMilliseconds(minimumIntervalForNonForceRefreshLocationInMS); + } + else + { + DefaultTrace.TraceError($"GlobalEndpointManager: Failed to parse {GlobalEndpointManager.MinimumIntervalForNonForceRefreshLocationInMS}; Value:{minimumIntervalForNonForceRefreshLocationInMSConfig}"); + } + } } public ReadOnlyCollection ReadEndpoints => this.locationCache.ReadEndpoints; @@ -91,12 +105,14 @@ public GlobalEndpointManager(IDocumentClientInternal owner, ConnectionPolicy con public static Task GetDatabaseAccountFromAnyLocationsAsync( Uri defaultEndpoint, IList? locations, - Func> getDatabaseAccountFn) + Func> getDatabaseAccountFn, + CancellationToken cancellationToken) { GetAccountPropertiesHelper threadSafeGetAccountHelper = new GetAccountPropertiesHelper( defaultEndpoint, locations?.GetEnumerator(), - getDatabaseAccountFn); + getDatabaseAccountFn, + cancellationToken); return threadSafeGetAccountHelper.GetAccountPropertiesAsync(); } @@ -106,7 +122,7 @@ public static Task GetDatabaseAccountFromAnyLocationsAsync( /// private class GetAccountPropertiesHelper { - private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource CancellationTokenSource; private readonly Uri DefaultEndpoint; private readonly IEnumerator? Locations; private readonly Func> GetDatabaseAccountFn; @@ -117,11 +133,13 @@ private class GetAccountPropertiesHelper public GetAccountPropertiesHelper( Uri defaultEndpoint, IEnumerator? locations, - Func> getDatabaseAccountFn) + Func> getDatabaseAccountFn, + CancellationToken cancellationToken) { this.DefaultEndpoint = defaultEndpoint; this.Locations = locations; this.GetDatabaseAccountFn = getDatabaseAccountFn; + this.CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); } public async Task GetAccountPropertiesAsync() @@ -218,7 +236,7 @@ private async Task TryGetAccountPropertiesFromAllLocationsAsync() { if (location == null) { - DefaultTrace.TraceCritical("location is null for TryMoveNextLocationThreadSafe"); + DefaultTrace.TraceCritical("GlobalEndpointManager: location is null for TryMoveNextLocationThreadSafe"); return; } @@ -261,6 +279,7 @@ private async Task GetAndUpdateAccountPropertiesAsync(Uri endpoint) { if (this.CancellationTokenSource.IsCancellationRequested) { + this.LastTransientException = new OperationCanceledException("GlobalEndpointManager: Get account information canceled"); return; } @@ -274,10 +293,10 @@ private async Task GetAndUpdateAccountPropertiesAsync(Uri endpoint) } catch (Exception e) { - DefaultTrace.TraceInformation("Fail to reach gateway endpoint {0}, {1}", endpoint, e.ToString()); + DefaultTrace.TraceInformation("GlobalEndpointManager: Fail to reach gateway endpoint {0}, {1}", endpoint, e.ToString()); if (GetAccountPropertiesHelper.IsNonRetriableException(e)) { - DefaultTrace.TraceInformation("Exception is not retriable"); + DefaultTrace.TraceInformation("GlobalEndpointManager: Exception is not retriable"); this.CancellationTokenSource.Cancel(); this.NonRetriableException = e; } @@ -315,14 +334,14 @@ public string GetLocation(Uri endpoint) public virtual void MarkEndpointUnavailableForRead(Uri endpoint) { - DefaultTrace.TraceInformation("Marking endpoint {0} unavailable for read", endpoint); + DefaultTrace.TraceInformation("GlobalEndpointManager: Marking endpoint {0} unavailable for read", endpoint); this.locationCache.MarkEndpointUnavailableForRead(endpoint); } public virtual void MarkEndpointUnavailableForWrite(Uri endpoint) { - DefaultTrace.TraceInformation("Marking endpoint {0} unavailable for Write", endpoint); + DefaultTrace.TraceInformation("GlobalEndpointManager: Marking endpoint {0} unavailable for Write", endpoint); this.locationCache.MarkEndpointUnavailableForWrite(endpoint); } @@ -345,75 +364,53 @@ public void Dispose() } } - public virtual async Task RefreshLocationAsync(AccountProperties databaseAccount, bool forceRefresh = false) + public virtual void InitializeAccountPropertiesAndStartBackgroundRefresh(AccountProperties databaseAccount) { if (this.cancellationTokenSource.IsCancellationRequested) { return; } - if (forceRefresh) - { - AccountProperties refreshedDatabaseAccount = await this.RefreshDatabaseAccountInternalAsync(); + this.locationCache.OnDatabaseAccountRead(databaseAccount); - this.locationCache.OnDatabaseAccountRead(refreshedDatabaseAccount); + if (this.isBackgroundAccountRefreshActive) + { return; } - lock (this.refreshLock) + lock (this.backgroundAccountRefreshLock) { - if (this.isRefreshing) + if (this.isBackgroundAccountRefreshActive) { return; } - this.isRefreshing = true; + this.isBackgroundAccountRefreshActive = true; } try { - await this.RefreshLocationPrivateAsync(databaseAccount); + this.StartLocationBackgroundRefreshLoop(); } catch { - this.isRefreshing = false; + this.isBackgroundAccountRefreshActive = false; throw; } } - private async Task RefreshLocationPrivateAsync(AccountProperties databaseAccount) + public virtual async Task RefreshLocationAsync(bool forceRefresh = false) { if (this.cancellationTokenSource.IsCancellationRequested) { return; } - DefaultTrace.TraceInformation("RefreshLocationAsync() refreshing locations"); - - if (databaseAccount != null) - { - this.locationCache.OnDatabaseAccountRead(databaseAccount); - } - - if (this.locationCache.ShouldRefreshEndpoints(out bool canRefreshInBackground)) - { - if (databaseAccount == null && !canRefreshInBackground) - { - databaseAccount = await this.RefreshDatabaseAccountInternalAsync(); - - this.locationCache.OnDatabaseAccountRead(databaseAccount); - } - - this.StartRefreshLocationTimer(); - } - else - { - this.isRefreshing = false; - } + await this.RefreshDatabaseAccountInternalAsync(forceRefresh: forceRefresh); } #pragma warning disable VSTHRD100 // Avoid async void methods - private async void StartRefreshLocationTimer() + private async void StartLocationBackgroundRefreshLoop() #pragma warning restore VSTHRD100 // Avoid async void methods { if (this.cancellationTokenSource.IsCancellationRequested) @@ -421,27 +418,47 @@ private async void StartRefreshLocationTimer() return; } + DefaultTrace.TraceInformation("GlobalEndpointManager: StartLocationBackgroundRefreshWithTimer() refreshing locations"); + + if (!this.locationCache.ShouldRefreshEndpoints(out bool canRefreshInBackground)) + { + if (!canRefreshInBackground) + { + DefaultTrace.TraceInformation("GlobalEndpointManager: StartLocationBackgroundRefreshWithTimer() stropped."); + lock (this.backgroundAccountRefreshLock) + { + this.isBackgroundAccountRefreshActive = false; + } + + return; + } + } + try { await Task.Delay(this.backgroundRefreshLocationTimeIntervalInMS, this.cancellationTokenSource.Token); - DefaultTrace.TraceInformation("StartRefreshLocationTimerAsync() - Invoking refresh"); + DefaultTrace.TraceInformation("GlobalEndpointManager: StartLocationBackgroundRefreshWithTimer() - Invoking refresh"); - AccountProperties databaseAccount = await this.RefreshDatabaseAccountInternalAsync(); + if (this.cancellationTokenSource.IsCancellationRequested) + { + return; + } - await this.RefreshLocationPrivateAsync(databaseAccount); + await this.RefreshDatabaseAccountInternalAsync(forceRefresh: false); } catch (Exception ex) { + DefaultTrace.TraceCritical("GlobalEndpointManager: StartLocationBackgroundRefreshWithTimer() - Unable to refresh database account from any location. Exception: {0}", ex.ToString()); + if (this.cancellationTokenSource.IsCancellationRequested && (ex is TaskCanceledException || ex is ObjectDisposedException)) { return; } - - DefaultTrace.TraceCritical("StartRefreshLocationTimerAsync() - Unable to refresh database account from any location. Exception: {0}", ex.ToString()); - - this.StartRefreshLocationTimer(); } + + // Call itself to create a loop to continuously do background refresh every 5 minutes + this.StartLocationBackgroundRefreshLoop(); } private Task GetDatabaseAccountAsync(Uri serviceEndpoint) @@ -455,19 +472,74 @@ private void OnPreferenceChanged(object sender, NotifyCollectionChangedEventArgs this.connectionPolicy.PreferredLocations)); } - private Task RefreshDatabaseAccountInternalAsync() + /// + /// Thread safe refresh account and location info. + /// + private async Task RefreshDatabaseAccountInternalAsync(bool forceRefresh) { + if (this.cancellationTokenSource.IsCancellationRequested) + { + return; + } + + if (this.SkipRefresh(forceRefresh)) + { + return; + } + + lock (this.isAccountRefreshInProgressLock) + { + // Check again if should refresh after obtaining the lock + if (this.SkipRefresh(forceRefresh)) + { + return; + } + + // If the refresh is already in progress just return. No reason to do another refresh. + if (this.isAccountRefreshInProgress) + { + return; + } + + this.isAccountRefreshInProgress = true; + } + + try + { #nullable disable // Needed because AsyncCache does not have nullable enabled - return this.databaseAccountCache.GetAsync( - key: string.Empty, - obsoleteValue: null, - singleValueInitFunc: () => GlobalEndpointManager.GetDatabaseAccountFromAnyLocationsAsync( - this.defaultEndpoint, - this.connectionPolicy.PreferredLocations, - this.GetDatabaseAccountAsync), - cancellationToken: this.cancellationTokenSource.Token, - forceRefresh: true); + AccountProperties accountProperties = await this.databaseAccountCache.GetAsync( + key: string.Empty, + obsoleteValue: null, + singleValueInitFunc: () => GlobalEndpointManager.GetDatabaseAccountFromAnyLocationsAsync( + this.defaultEndpoint, + this.connectionPolicy.PreferredLocations, + this.GetDatabaseAccountAsync, + this.cancellationTokenSource.Token), + cancellationToken: this.cancellationTokenSource.Token, + forceRefresh: true); + + this.LastBackgroundRefreshUtc = DateTime.UtcNow; + this.locationCache.OnDatabaseAccountRead(accountProperties); #nullable enable + } + finally + { + lock (this.isAccountRefreshInProgressLock) + { + this.isAccountRefreshInProgress = false; + } + } + } + + /// + /// If the account is currently refreshing or the last refresh occurred less than the minimum time + /// just return. This is used to avoid refreshing to often and preventing to much pressure on the gateway. + /// + private bool SkipRefresh(bool forceRefresh) + { + TimeSpan timeSinceLastRefresh = DateTime.UtcNow - this.LastBackgroundRefreshUtc; + return (this.isAccountRefreshInProgress || this.MinTimeBetweenAccountRefresh > timeSinceLastRefresh) + && !forceRefresh; } } } diff --git a/Microsoft.Azure.Cosmos/src/Routing/IGlobalEndpointManager.cs b/Microsoft.Azure.Cosmos/src/Routing/IGlobalEndpointManager.cs index 061a257682..dd3d8a2611 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/IGlobalEndpointManager.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/IGlobalEndpointManager.cs @@ -27,6 +27,8 @@ internal interface IGlobalEndpointManager : IDisposable bool CanUseMultipleWriteLocations(DocumentServiceRequest request); - Task RefreshLocationAsync(AccountProperties databaseAccount, bool forceRefresh = false); + void InitializeAccountPropertiesAndStartBackgroundRefresh(AccountProperties databaseAccount); + + Task RefreshLocationAsync(bool forceRefresh = false); } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs index b73fc20349..bcc7a3df12 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionKeyRangeCache.cs @@ -27,12 +27,12 @@ internal class PartitionKeyRangeCache : IRoutingMapProvider, ICollectionRoutingM private readonly AsyncCache routingMapCache; - private readonly IAuthorizationTokenProvider authorizationTokenProvider; + private readonly ICosmosAuthorizationTokenProvider authorizationTokenProvider; private readonly IStoreModel storeModel; private readonly CollectionCache collectionCache; public PartitionKeyRangeCache( - IAuthorizationTokenProvider authorizationTokenProvider, + ICosmosAuthorizationTokenProvider authorizationTokenProvider, IStoreModel storeModel, CollectionCache collectionCache) { @@ -248,12 +248,13 @@ private async Task ExecutePartitionKeyRangeReadChangeFe string authorizationToken = null; try { - authorizationToken = (await this.authorizationTokenProvider.GetUserAuthorizationAsync( + authorizationToken = await this.authorizationTokenProvider.GetUserAuthorizationTokenAsync( request.ResourceAddress, PathsHelper.GetResourcePath(request.ResourceType), HttpConstants.HttpMethods.Get, request.Headers, - AuthorizationTokenType.PrimaryMasterKey)).token; + AuthorizationTokenType.PrimaryMasterKey, + childTrace); } catch (UnauthorizedException) { diff --git a/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs b/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs index c645d4cc55..89df77afd1 100644 --- a/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs +++ b/Microsoft.Azure.Cosmos/src/Routing/PartitionRoutingHelper.cs @@ -66,8 +66,7 @@ public static IReadOnlyList> GetProvidedPartitionKeyRanges( } PartitionedQueryExecutionInfo queryExecutionInfo = tryGetPartitionQueryExecutionInfo.Result; - if (queryExecutionInfo == null || - queryExecutionInfo.QueryRanges == null || + if (queryExecutionInfo?.QueryRanges == null || queryExecutionInfo.QueryInfo == null || queryExecutionInfo.QueryRanges.Any(range => range.Min == null || range.Max == null)) { @@ -435,7 +434,7 @@ public virtual Range ExtractPartitionKeyRangeFromContinuationToken(IName } } - if (initialContinuationToken != null && initialContinuationToken.Range != null) + if (initialContinuationToken?.Range != null) { range = initialContinuationToken.Range; } diff --git a/Microsoft.Azure.Cosmos/src/TimerWheel/TimerWheelTimerCore.cs b/Microsoft.Azure.Cosmos/src/TimerWheel/TimerWheelTimerCore.cs index 198bb79c6f..66b3ce5883 100644 --- a/Microsoft.Azure.Cosmos/src/TimerWheel/TimerWheelTimerCore.cs +++ b/Microsoft.Azure.Cosmos/src/TimerWheel/TimerWheelTimerCore.cs @@ -27,7 +27,7 @@ internal TimerWheelTimerCore( this.timerWheel = timerWheel ?? throw new ArgumentNullException(nameof(timerWheel)); this.Timeout = timeoutPeriod; - this.taskCompletionSource = new TaskCompletionSource(); + this.taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); this.memberLock = new object(); } diff --git a/Microsoft.Azure.Cosmos/src/Tracing/ITrace.cs b/Microsoft.Azure.Cosmos/src/Tracing/ITrace.cs index 4ff13a1acb..751af98467 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/ITrace.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/ITrace.cs @@ -113,5 +113,11 @@ ITrace StartChild( /// The key to associate the datum. /// The datum itself. void AddDatum(string key, object value); + + /// + /// Adds a trace children that is already completed. + /// + /// Existing trace. + void AddChild(ITrace trace); } } diff --git a/Microsoft.Azure.Cosmos/src/Tracing/NoOpTrace.cs b/Microsoft.Azure.Cosmos/src/Tracing/NoOpTrace.cs index 3b6c68426b..f28c8c3019 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/NoOpTrace.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/NoOpTrace.cs @@ -76,5 +76,10 @@ public void AddDatum(string key, object value) { // NoOp } + + public void AddChild(ITrace trace) + { + // NoOp + } } } diff --git a/Microsoft.Azure.Cosmos/src/Tracing/Trace.cs b/Microsoft.Azure.Cosmos/src/Tracing/Trace.cs index cfd4aea1ef..5a5d7df45c 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/Trace.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/Trace.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Cosmos.Tracing internal sealed class Trace : ITrace { - private readonly List children; + private readonly List children; private readonly Dictionary data; private readonly Stopwatch stopwatch; @@ -30,7 +30,7 @@ private Trace( this.Level = level; this.Component = component; this.Parent = parent; - this.children = new List(); + this.children = new List(); this.data = new Dictionary(); } @@ -89,12 +89,17 @@ public ITrace StartChild( component: component, parent: this); + this.AddChild(child); + + return child; + } + + public void AddChild(ITrace child) + { lock (this.children) { this.children.Add(child); } - - return child; } public static Trace GetRootTrace(string name) diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ConsistencyConfig.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ConsistencyConfig.cs index dfbffe8da3..ae1dd7b740 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ConsistencyConfig.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceData/ConsistencyConfig.cs @@ -14,16 +14,16 @@ public ConsistencyConfig( ConsistencyLevel? consistencyLevel, IReadOnlyList preferredRegions) { - this.ConsistencyLevel = consistencyLevel.GetValueOrDefault(); + this.ConsistencyLevel = consistencyLevel; this.PreferredRegions = preferredRegions; this.lazyString = new Lazy(() => string.Format(CultureInfo.InvariantCulture, "(consistency: {0}, prgns:[{1}])", - consistencyLevel.GetValueOrDefault(), + consistencyLevel?.ToString() ?? "NotSet", ConsistencyConfig.PreferredRegionsInternal(preferredRegions))); this.lazyJsonString = new Lazy(() => Newtonsoft.Json.JsonConvert.SerializeObject(this)); } - public ConsistencyLevel ConsistencyLevel { get; } + public ConsistencyLevel? ConsistencyLevel { get; } public IReadOnlyList PreferredRegions { get; } private readonly Lazy lazyString; diff --git a/Microsoft.Azure.Cosmos/src/Tracing/TraceJoiner.cs b/Microsoft.Azure.Cosmos/src/Tracing/TraceJoiner.cs index 61a5376d16..acbcdc1994 100644 --- a/Microsoft.Azure.Cosmos/src/Tracing/TraceJoiner.cs +++ b/Microsoft.Azure.Cosmos/src/Tracing/TraceJoiner.cs @@ -89,9 +89,14 @@ public ITrace StartChild(string name, [CallerMemberName] string memberName = "", public ITrace StartChild(string name, TraceComponent component, TraceLevel level, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { ITrace child = Trace.GetRootTrace(name, component, level, memberName, sourceFilePath, sourceLineNumber); - this.children.Add(child); + this.AddChild(child); return child; } + + public void AddChild(ITrace trace) + { + this.children.Add(trace); + } } } } diff --git a/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs b/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs index 9e0114b40f..477d9689a2 100644 --- a/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs +++ b/Microsoft.Azure.Cosmos/src/UserAgentContainer.cs @@ -31,10 +31,14 @@ internal override string BaseUserAgent } } - internal void SetFeatures(string features) + internal void SetFeatures( + string features, + string regionConfiguration) { // Regenerate base user agent to account for features - this.cosmosBaseUserAgent = this.CreateBaseUserAgentString(features); + this.cosmosBaseUserAgent = this.CreateBaseUserAgentString( + features: features, + regionConfiguration: regionConfiguration); this.Suffix = string.Empty; } @@ -55,7 +59,9 @@ protected virtual void GetEnvironmentInformation( runtimeFramework = environmentInformation.RuntimeFramework; } - private string CreateBaseUserAgentString(string features = null) + private string CreateBaseUserAgentString( + string features = null, + string regionConfiguration = null) { this.GetEnvironmentInformation( out string clientVersion, @@ -74,6 +80,11 @@ private string CreateBaseUserAgentString(string features = null) // Do not change the cosmos-netstandard-sdk as it is required for reporting string baseUserAgent = $"cosmos-netstandard-sdk/{clientVersion}" + Regex.Replace($"|{directVersion}|{clientId}|{processArchitecture}|{operatingSystem}|{runtimeFramework}|", @"[^0-9a-zA-Z\.\|\-]+", " "); + if (!string.IsNullOrEmpty(regionConfiguration)) + { + baseUserAgent += $"{regionConfiguration}|"; + } + if (!string.IsNullOrEmpty(features)) { baseUserAgent += $"F {features}|"; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml index ecd8b7733e..71d3d31f17 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/EndToEndTraceWriterBaselineTests.BulkOperationsAsync.xml @@ -7,7 +7,7 @@ CosmosClient bulkClient = TestCommon.CreateCosmosClient(builder => builder.WithBulkExecution(true)); Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); List>> createItemsTasks = new List>>(); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 10; i++) { ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); @@ -25,8718 +25,47 @@ traces.Add(trace); } - ITrace joinedTrace = TraceJoiner.JoinTraces(traces); ]]> - + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) { - "name": "Batch Dispatch Async", + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation + builder.WithBulkExecution(true)); + Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); + List>> createItemsTasks = new List>>(); + for (int i = 0; i < 10; i++) + { + ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); + createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); + } + + await Task.WhenAll(createItemsTasks); + + List traces = new List(); + foreach (Task> createTask in createItemsTasks) + { + ItemResponse itemResponse = await createTask; + Assert.IsNotNull(itemResponse); + + ITrace trace = ((CosmosTraceDiagnostics)itemResponse.Diagnostics).Value; + traces.Add(trace); + } + +]]> + + + + + + + + + Bulk Operation With Throttle + + builder.WithThrottlingRetryOptions(TimeSpan.FromSeconds(5), 3) + .WithBulkExecution(true) + .WithTransportClientHandlerFactory(transportClient => new TransportClientWrapper( + transportClient, + (uri, resourceOperation, request) => TransportClientHelper.ReturnThrottledStoreResponseOnItemOperation( + uri, + resourceOperation, + request, + exceptionActivityId, + errorMessage))) + ); + + ItemRequestOptions requestOptions = new ItemRequestOptions(); + Container containerWithThrottleException = throttleClient.GetContainer( + database.Id, + container.Id); + + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + ITrace trace = null; + try + { + ItemResponse createResponse = await containerWithThrottleException.CreateItemAsync( + item: testItem, + partitionKey: new PartitionKey(testItem.id), + requestOptions: requestOptions); + Assert.Fail("Should have thrown a throttling exception"); + } + catch (CosmosException ce) when ((int)ce.StatusCode == (int)Documents.StatusCodes.TooManyRequests) + { + trace = ((CosmosTraceDiagnostics)ce.Diagnostics).Value; + } +]]> + + + + > tasks = new List>(); for (int i = 0; i < 100; i++) { - tasks.Add(executor.AddAsync(CreateItem(i.ToString()), null, default(CancellationToken))); + tasks.Add(executor.AddAsync(CreateItem(i.ToString()), NoOpTrace.Singleton)); } await Task.WhenAll(tasks); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/BaseChangeFeedClientHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/BaseChangeFeedClientHelper.cs index 4c54060e51..36181d5f32 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/BaseChangeFeedClientHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/BaseChangeFeedClientHelper.cs @@ -16,7 +16,7 @@ public class BaseChangeFeedClientHelper : BaseCosmosClientHelper public async Task ChangeFeedTestInit() { - await base.TestInit(); + await base.TestInit(customizeClientBuilder: (builder) => builder.WithContentResponseOnWrite(false)); string PartitionKey = "/id"; ContainerResponse response = await this.database.CreateContainerAsync( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientConfigurationDiagnosticTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientConfigurationDiagnosticTest.cs index 32fcfd5129..a4c686e315 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientConfigurationDiagnosticTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ClientConfigurationDiagnosticTest.cs @@ -1,6 +1,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using System; + using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Tracing; @@ -72,9 +73,24 @@ public void CleintConfigWithOptionsTest() Assert.AreEqual(gwConfig.MaxConnectionLimit, 20); ConsistencyConfig consistencyConfig = cosmosClient.ClientConfigurationTraceDatum.ConsistencyConfig; - Assert.AreEqual(consistencyConfig.ConsistencyLevel, ConsistencyLevel.Session); + Assert.AreEqual(consistencyConfig.ConsistencyLevel.Value, ConsistencyLevel.Session); } - + + [TestMethod] + public void ConsistencyConfigSerializationTest() + { + List preferredRegions = new List { "EastUS", "WestUs" }; + ConsistencyLevel consistencyLevel = ConsistencyLevel.Session; + + ConsistencyConfig consistencyConfig = new ConsistencyConfig(consistencyLevel, preferredRegions); + Assert.AreEqual(consistencyConfig.ToString(), "(consistency: Session, prgns:[EastUS, WestUs])"); + + ConsistencyConfig consistencyConfigWithNull = new ConsistencyConfig(consistencyLevel: null, + preferredRegions: null); + + Assert.AreEqual(consistencyConfigWithNull.ToString(), "(consistency: NotSet, prgns:[])"); + } + [TestMethod] public async Task CachedSerializationTest() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAadTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAadTests.cs index e493ba881c..d227db63fa 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAadTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosAadTests.cs @@ -156,7 +156,7 @@ void GetAadTokenCallBack( simpleEmulatorTokenCredential, clientOptions)) { - Assert.AreEqual(3, getAadTokenCount); + Assert.AreEqual(2, getAadTokenCount); await Task.Delay(TimeSpan.FromSeconds(1)); ResponseMessage responseMessage = await aadClient.GetDatabase(Guid.NewGuid().ToString()).ReadStreamAsync(); Assert.IsNotNull(responseMessage); @@ -197,7 +197,7 @@ void GetAadTokenCallBack( simpleEmulatorTokenCredential, clientOptions)) { - Assert.AreEqual(3, getAadTokenCount); + Assert.AreEqual(2, getAadTokenCount); await Task.Delay(TimeSpan.FromSeconds(1)); try { @@ -205,7 +205,7 @@ void GetAadTokenCallBack( await aadClient.GetDatabase(Guid.NewGuid().ToString()).ReadStreamAsync(); Assert.Fail("Should throw auth error."); } - catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.Unauthorized) + catch (RequestFailedException ce) when (ce.Status == (int)HttpStatusCode.RequestTimeout) { Assert.IsNotNull(ce.Message); Assert.IsTrue(ce.ToString().Contains(errorMessage)); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 0385a4a9cf..fdf862438b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -1432,7 +1432,7 @@ public async Task ClientEncryptionPolicyTest() } [TestMethod] - public void ClientEncryptionPolicyFailureTest() + public async Task ClientEncryptionPolicyFailureTest() { string containerName = Guid.NewGuid().ToString(); string partitionKeyPath = "/users"; @@ -1498,6 +1498,34 @@ public void ClientEncryptionPolicyFailureTest() { Assert.IsTrue(ex.Message.Contains("Duplicate Path found.")); } + + try + { + Collection pathsToEncryptWithPartitionKey = new Collection() + { + new ClientEncryptionIncludedPath() + { + Path = partitionKeyPath, + ClientEncryptionKeyId = "dekId1", + EncryptionAlgorithm = "AEAD_AES_256_CBC_HMAC_SHA256", + EncryptionType = "Randomized" + }, + }; + + ContainerProperties setting = new ContainerProperties() + { + Id = containerName, + PartitionKey = new PartitionKeyDefinition() { Paths = new Collection { partitionKeyPath }, Kind = PartitionKind.Hash }, + ClientEncryptionPolicy = new ClientEncryptionPolicy(pathsToEncryptWithPartitionKey) + }; + + await this.cosmosDatabase.CreateContainerAsync(setting); + Assert.Fail("Creating container should have failed."); + } + catch (ArgumentException ex) + { + Assert.IsTrue(ex.Message.Contains("Paths which are part of the partition key may not be included in the ClientEncryptionPolicy.")); + } } private void ValidateCreateContainerResponseContract(ContainerResponse containerResponse) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosExceptionTests.cs new file mode 100644 index 0000000000..743bccc215 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosExceptionTests.cs @@ -0,0 +1,83 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Threading.Tasks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class CosmosUnexpectedExceptionTests : BaseCosmosClientHelper + { + private ContainerInternal Container = null; + + [TestInitialize] + public async Task TestInitialize() + { + await base.TestInit(); + string PartitionKey = "/pk"; + ContainerResponse response = await this.database.CreateContainerAsync( + new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey), + cancellationToken: this.cancellationToken); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Container); + Assert.IsNotNull(response.Resource); + this.Container = (ContainerInternal)response; + } + + [TestCleanup] + public async Task Cleanup() + { + await base.TestCleanup(); + } + + [TestMethod] + public async Task CheckTracesIncludedWithAllExceptionsTestAsync() + { + RequestHandlerHelper requestHandlerHelper = new RequestHandlerHelper(); + CosmosClient cosmosClient = TestCommon.CreateCosmosClient( + customizeClientBuilder: builder => builder.AddCustomHandlers(requestHandlerHelper)); + Container containerWithFailure = cosmosClient.GetContainer(this.database.Id, this.Container.Id); + + requestHandlerHelper.UpdateRequestMessage = (request) => throw new ObjectDisposedException("Mock ObjectDisposedException"); + await this.CheckForTracesAsync(containerWithFailure, isClientDisposed: false); + + cosmosClient.Dispose(); + requestHandlerHelper.UpdateRequestMessage = (request) => throw new ObjectDisposedException("Mock ObjectDisposedException"); + await this.CheckForTracesAsync(containerWithFailure, isClientDisposed: true); + } + + + private async Task CheckForTracesAsync( + Container container, + bool isClientDisposed) where ExceptionType : Exception + { + ToDoActivity toDoActivity = ToDoActivity.CreateRandomToDoActivity(); + + try + { + await container.CreateItemAsync( + toDoActivity, + new Cosmos.PartitionKey(toDoActivity.pk)); + + Assert.Fail("Should have thrown"); + } + catch (ExceptionType e) + { + if (isClientDisposed) + { + Assert.IsTrue(e.Message.Contains("Cannot access a disposed 'CosmosClient'. Follow best practices and use the CosmosClient as a singleton.")); + } + else + { + Assert.IsTrue(e.Message.Contains("The CosmosClient is still active and NOT disposed of. CosmosClient Endpoint:")); + } + + Assert.IsFalse(e.Message.Contains("CosmosDiagnostics")); + Assert.IsTrue(e.ToString().Contains("Client Configuration")); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 1a7cbf248c..94f807e95a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -2092,7 +2092,7 @@ public async Task ItemPatchViaGatewayTest() } [TestMethod] - public async Task ItemPatchCustomSerilizerTest() + public async Task ItemPatchCustomSerializerTest() { CosmosClientOptions clientOptions = new CosmosClientOptions() { @@ -2115,9 +2115,23 @@ public async Task ItemPatchCustomSerilizerTest() }; DateTime patchDate = new DateTime(2020, 07, 01, 01, 02, 03); - List patchOperations = new List() + Stream patchDateStreamInput = new CosmosJsonDotNetSerializer().ToStream(patchDate); + string streamDateJson; + using (Stream stream = new MemoryStream()) + { + patchDateStreamInput.CopyTo(stream); + stream.Position = 0; + patchDateStreamInput.Position = 0; + using (StreamReader streamReader = new StreamReader(stream)) + { + streamDateJson = streamReader.ReadToEnd(); + } + } + + List patchOperations = new List() { - PatchOperation.Add("/date", patchDate) + PatchOperation.Add("/date", patchDate), + PatchOperation.Add("/dateStream", patchDateStreamInput) }; ItemResponse response = await containerInternal.PatchItemAsync( @@ -2140,6 +2154,60 @@ public async Task ItemPatchCustomSerilizerTest() Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); Assert.IsNotNull(response.Resource); Assert.IsTrue(dateJson.Contains(response.Resource["date"].ToString())); + Assert.AreEqual(patchDate.ToString(), response.Resource["dateStream"].ToString()); + Assert.AreNotEqual(response.Resource["date"], response.Resource["dateStream"]); + } + + [TestMethod] + public async Task ItemPatchStreamInputTest() + { + dynamic testItem = new + { + id = "test", + cost = (double?)null, + totalCost = 98.2789, + pk = "MyCustomStatus", + taskNum = 4909, + itemIds = new int[] { 1, 5, 10 }, + itemCode = new byte?[5] { 0x16, (byte)'\0', 0x3, null, (byte)'}' }, + }; + + // Create item + await this.Container.CreateItemAsync(item: testItem); + ContainerInternal containerInternal = (ContainerInternal)this.Container; + + dynamic testItemUpdated = new + { + cost = 100, + totalCost = 198.2789, + taskNum = 4910, + itemCode = new byte?[3] { 0x14, (byte)'\0', (byte)'{' } + }; + + CosmosJsonDotNetSerializer cosmosJsonDotNetSerializer = new CosmosJsonDotNetSerializer(); + + List patchOperations = new List() + { + PatchOperation.Replace("/cost", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.cost)), + PatchOperation.Replace("/totalCost", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.totalCost)), + PatchOperation.Replace("/taskNum", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.taskNum)), + PatchOperation.Replace("/itemCode", cosmosJsonDotNetSerializer.ToStream(testItemUpdated.itemCode)), + }; + + ItemResponse response = await containerInternal.PatchItemAsync( + id: testItem.id, + partitionKey: new Cosmos.PartitionKey(testItem.pk), + patchOperations: patchOperations); + + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + Assert.IsNotNull(response.Resource); + + Assert.AreEqual(testItemUpdated.cost.ToString(), response.Resource.cost.ToString()); + Assert.AreEqual(testItemUpdated.totalCost.ToString(), response.Resource.totalCost.ToString()); + Assert.AreEqual(testItemUpdated.taskNum.ToString(), response.Resource.taskNum.ToString()); + Assert.AreEqual(testItemUpdated.itemCode[0].ToString(), response.Resource.itemCode[0].ToString()); + Assert.AreEqual(testItemUpdated.itemCode[1].ToString(), response.Resource.itemCode[1].ToString()); + Assert.AreEqual(testItemUpdated.itemCode[2].ToString(), response.Resource.itemCode[2].ToString()); } // Read write non partition Container item. diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosNullReferenceExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosNullReferenceExceptionTests.cs new file mode 100644 index 0000000000..b91b43fd64 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosNullReferenceExceptionTests.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Documents; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class CosmosNullReferenceExceptionTests : BaseCosmosClientHelper + { + private ContainerInternal container = null; + + [TestInitialize] + public async Task TestInitialize() + { + await base.TestInit(); + string PartitionKey = "/pk"; + ContainerResponse response = await this.database.CreateContainerAsync( + new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPath: PartitionKey), + cancellationToken: this.cancellationToken); + Assert.IsNotNull(response); + Assert.IsNotNull(response.Container); + Assert.IsNotNull(response.Resource); + this.container = (ContainerInternal)response; + } + + [TestCleanup] + public async Task Cleanup() + { + await base.TestCleanup(); + } + + [TestMethod] + public async Task CosmosEndToEndNullReferenceExceptionTestAsync() + { + string errorMessage = Guid.NewGuid().ToString(); + RequestHandlerHelper requestHandlerHelper = new RequestHandlerHelper + { + UpdateRequestMessage = (request) => throw new NullReferenceException(errorMessage) + }; + + using CosmosClient client = TestCommon.CreateCosmosClient(builder => builder.AddCustomHandlers(requestHandlerHelper)); + Container containerWithNullRef = client.GetContainer(this.database.Id, this.container.Id); + + try + { + ToDoActivity toDoActivity = ToDoActivity.CreateRandomToDoActivity(); + await containerWithNullRef.CreateItemAsync(toDoActivity); + Assert.Fail("Create should throw a null reference exception"); + } + catch(NullReferenceException nre) + { + Assert.AreEqual(typeof(CosmosNullReferenceException), nre.GetType()); + Assert.IsTrue(nre.Message.Contains("CreateItemAsync")); + string cosmosToString = nre.ToString(); + Assert.IsFalse(cosmosToString.Contains("Microsoft.Azure.Cosmos.CosmosNullReferenceException"), $"The internal wrapper exception should not be exposed to users. {cosmosToString}"); + Assert.IsTrue(cosmosToString.Contains(errorMessage)); + Assert.IsTrue(cosmosToString.Contains("CreateItemAsync")); + } + + try + { + FeedIterator iterator = containerWithNullRef.GetItemQueryIterator("select * from T"); + await iterator.ReadNextAsync(); + Assert.Fail("Create should throw a null reference exception"); + } + catch (NullReferenceException nre) + { + Assert.AreEqual(typeof(CosmosNullReferenceException), nre.GetType()); + Assert.IsTrue(nre.Message.Contains("Typed FeedIterator ReadNextAsync")); + string cosmosToString = nre.ToString(); + Assert.IsFalse(cosmosToString.Contains("Microsoft.Azure.Cosmos.CosmosNullReferenceException"), $"The internal wrapper exception should not be exposed to users. {cosmosToString}"); + Assert.IsTrue(cosmosToString.Contains(errorMessage)); + Assert.IsTrue(cosmosToString.Contains("Typed FeedIterator ReadNextAsync")); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs index 9e778d9e00..526cbfeebe 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosReadManyItemsTests.cs @@ -363,7 +363,7 @@ public async Task ReadManyMultiplePK() { IReadOnlyList pkPaths = new List { "/pk", "/description" }; ContainerProperties containerSettings = new ContainerProperties(id: Guid.NewGuid().ToString(), partitionKeyPaths: pkPaths); - Container container = await this.database.CreateContainerAsync(this.containerSettings); + Container container = await this.database.CreateContainerAsync(containerSettings); for (int i = 0; i < 5; i++) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index c56544a224..edcac44b3d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -734,6 +734,78 @@ public async Task ChangeFeedIteratorCore_WithFullFidelity() Assert.IsTrue(hasDeletes, "No metadata for delete operationType found"); } + [TestMethod] + public async Task TestCancellationTokenAsync() + { + CancellationTokenRequestHandler cancellationTokenHandler = new CancellationTokenRequestHandler(); + + ContainerInternal itemsCore = await this.InitializeContainerAsync(); + await this.CreateRandomItems(itemsCore, 100, randomPartitionKey: true); + + // Inject validating handler + RequestHandler currentInnerHandler = this.cosmosClient.RequestHandler.InnerHandler; + this.cosmosClient.RequestHandler.InnerHandler = cancellationTokenHandler; + cancellationTokenHandler.InnerHandler = currentInnerHandler; + + { + // Test to see if the token flows to the pipeline + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.Beginning(), + ChangeFeedMode.Incremental) as ChangeFeedIteratorCore; + await feedIterator.ReadNextAsync(cancellationTokenSource.Token); + Assert.AreEqual(cancellationTokenSource.Token, cancellationTokenHandler.LastUsedToken, "The token passed did not reach the pipeline"); + } + + // See if cancellation token is honored for first request + try + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.Beginning(), + ChangeFeedMode.Incremental) as ChangeFeedIteratorCore; + await feedIterator.ReadNextAsync(cancellationTokenSource.Token); + + Assert.Fail("Expected exception."); + } + catch (OperationCanceledException) + { + } + + // See if cancellation token is honored for second request + try + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.Beginning(), + ChangeFeedMode.Incremental) as ChangeFeedIteratorCore; + await feedIterator.ReadNextAsync(); + await feedIterator.ReadNextAsync(cancellationTokenSource.Token); + Assert.Fail("Expected exception."); + } + catch (OperationCanceledException) + { + } + + // See if cancellation token is honored mid draining + try + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + ChangeFeedIteratorCore feedIterator = itemsCore.GetChangeFeedStreamIterator( + ChangeFeedStartFrom.Beginning(), + ChangeFeedMode.Incremental) as ChangeFeedIteratorCore; + await feedIterator.ReadNextAsync(cancellationTokenSource.Token); + cancellationTokenSource.Cancel(); + await feedIterator.ReadNextAsync(cancellationTokenSource.Token); + Assert.Fail("Expected exception."); + } + catch (OperationCanceledException) + { + } + } + private async Task> CreateRandomItems(ContainerInternal container, int pkCount, int perPKItemCount = 1, bool randomPartitionKey = true) { Assert.IsFalse(!randomPartitionKey && perPKItemCount > 1); @@ -771,5 +843,16 @@ public class ToDoActivityMetadata [JsonProperty("operationType")] public string operationType { get; set; } } + + private class CancellationTokenRequestHandler : RequestHandler + { + public CancellationToken LastUsedToken { get; private set; } + + public override Task SendAsync(RequestMessage request, CancellationToken cancellationToken) + { + this.LastUsedToken = cancellationToken; + return base.SendAsync(request, cancellationToken); + } + } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs index d505b36d2b..741e12ca7d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Fluent/ContainerSettingsTests.cs @@ -137,7 +137,7 @@ public async Task ContainerContractTest() Assert.AreEqual(4, spatialPath.SpatialTypes.Count); // All SpatialTypes are returned Assert.AreEqual(1, responseProperties.ClientEncryptionPolicy.IncludedPaths.Count()); - Assert.AreEqual(1, responseProperties.ClientEncryptionPolicy.PolicyFormatVersion); + Assert.IsTrue(responseProperties.ClientEncryptionPolicy.PolicyFormatVersion <= 1); ClientEncryptionIncludedPath clientEncryptionIncludedPath = responseProperties.ClientEncryptionPolicy.IncludedPaths.First(); Assert.IsTrue(this.VerifyClientEncryptionIncludedPath(clientEncryptionIncludedPath1, clientEncryptionIncludedPath)); } @@ -699,7 +699,25 @@ public async Task WithClientEncryptionPolicyFailureTest() } catch (ArgumentException ex) { - Assert.IsTrue(ex.Message.Contains("EncryptionType should be either 'Deterministic' or 'Randomized' or 'Plaintext'.")); + Assert.IsTrue(ex.Message.Contains("EncryptionType should be either 'Deterministic' or 'Randomized'. ")); + } + + path1.EncryptionType = "Plaintext"; + + // Invalid EncryptionType + try + { + ContainerResponse containerResponse = await this.database.DefineContainer(containerName, partitionKeyPath) + .WithClientEncryptionPolicy() + .WithIncludedPath(path1) + .Attach() + .CreateAsync(); + + Assert.Fail("CreateCollection with invalid ClientEncryptionPolicy should have failed."); + } + catch (ArgumentException ex) + { + Assert.IsTrue(ex.Message.Contains("EncryptionType should be either 'Deterministic' or 'Randomized'. ")); } path1.EncryptionType = "Deterministic"; @@ -717,7 +735,7 @@ public async Task WithClientEncryptionPolicyFailureTest() } catch (ArgumentException ex) { - Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'.")); + Assert.IsTrue(ex.Message.Contains("EncryptionAlgorithm should be 'AEAD_AES_256_CBC_HMAC_SHA256'. ")); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/GatewayTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/GatewayTests.cs index 527cb0351e..f63d52429c 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/GatewayTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/GatewayTests.cs @@ -3413,7 +3413,7 @@ public static string DumpFullExceptionMessage(Exception e) while (e != null) { DocumentClientException docException = e as DocumentClientException; - if (docException != null && docException.Error != null) + if (docException?.Error != null) { exceptionMessage.Append("Code : " + docException.Error.Code); if (docException.Error.ErrorDetails != null) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs index 02194958a5..47d10cf092 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Tracing/EndToEndTraceWriterBaselineTests.cs @@ -26,22 +26,17 @@ public sealed class EndToEndTraceWriterBaselineTests : BaselineTests builder.WithBulkExecution(true)); Container bulkContainer = bulkClient.GetContainer(database.Id, container.Id); List>> createItemsTasks = new List>>(); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 10; i++) { ToDoActivity item = ToDoActivity.CreateRandomToDoActivity(pk: pkValue); createItemsTasks.Add(bulkContainer.CreateItemAsync(item, new PartitionKey(item.id))); @@ -897,13 +895,60 @@ public async Task BulkOperationsAsync() traces.Add(trace); } - ITrace joinedTrace = TraceJoiner.JoinTraces(traces); endLineNumber = GetLineNumber(); - inputs.Add(new Input("Bulk Operation", joinedTrace, startLineNumber, endLineNumber)); + foreach (ITrace trace in traces) + { + inputs.Add(new Input("Bulk Operation", trace, startLineNumber, endLineNumber)); + } } //---------------------------------------------------------------- + //---------------------------------------------------------------- + // Bulk with retry on throttle + //---------------------------------------------------------------- + { + startLineNumber = GetLineNumber(); + string errorMessage = "Mock throttle exception" + Guid.NewGuid().ToString(); + Guid exceptionActivityId = Guid.NewGuid(); + // Set a small retry count to reduce test time + CosmosClient throttleClient = TestCommon.CreateCosmosClient(builder => + builder.WithThrottlingRetryOptions(TimeSpan.FromSeconds(5), 3) + .WithBulkExecution(true) + .WithTransportClientHandlerFactory(transportClient => new TransportClientWrapper( + transportClient, + (uri, resourceOperation, request) => TransportClientHelper.ReturnThrottledStoreResponseOnItemOperation( + uri, + resourceOperation, + request, + exceptionActivityId, + errorMessage))) + ); + + ItemRequestOptions requestOptions = new ItemRequestOptions(); + Container containerWithThrottleException = throttleClient.GetContainer( + database.Id, + container.Id); + + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + ITrace trace = null; + try + { + ItemResponse createResponse = await containerWithThrottleException.CreateItemAsync( + item: testItem, + partitionKey: new PartitionKey(testItem.id), + requestOptions: requestOptions); + Assert.Fail("Should have thrown a throttling exception"); + } + catch (CosmosException ce) when ((int)ce.StatusCode == (int)Documents.StatusCodes.TooManyRequests) + { + trace = ((CosmosTraceDiagnostics)ce.Diagnostics).Value; + } + endLineNumber = GetLineNumber(); + + inputs.Add(new Input("Bulk Operation With Throttle", trace, startLineNumber, endLineNumber)); + } + this.ExecuteTestSuite(inputs); } @@ -1202,7 +1247,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) private sealed class TraceForBaselineTesting : ITrace { public readonly Dictionary data; - public readonly List children; + public readonly List children; public TraceForBaselineTesting( string name, @@ -1214,7 +1259,7 @@ public TraceForBaselineTesting( this.Level = level; this.Component = component; this.Parent = parent; - this.children = new List(); + this.children = new List(); this.data = new Dictionary(); } @@ -1266,10 +1311,15 @@ public ITrace StartChild(string name, [CallerMemberName] string memberName = "", public ITrace StartChild(string name, TraceComponent component, TraceLevel level, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { TraceForBaselineTesting child = new TraceForBaselineTesting(name, level, component, parent: this); - this.children.Add(child); + this.AddChild(child); return child; } + public void AddChild(ITrace trace) + { + this.children.Add(trace); + } + public static TraceForBaselineTesting GetRootTrace() { return new TraceForBaselineTesting("Trace For Baseline Testing", TraceLevel.Info, TraceComponent.Unknown, parent: null); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs index 23254c36f2..464138ff66 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/UserAgentTests.cs @@ -96,6 +96,105 @@ public void VerifyUserAgentContent() Assert.AreEqual(envInfo.RuntimeFramework, values[5]); } + [TestMethod] + public async Task VerifyUserAgentWithRegionConfiguration() + { + string databaseName = Guid.NewGuid().ToString(); + string containerName = Guid.NewGuid().ToString(); + + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions(); + + // N - None. The user did not configure anything + string userAgentContentToValidate = "|N|"; + await this.ValidateUserAgentStringAsync( + cosmosClientOptions, + userAgentContentToValidate, + databaseName, + containerName); + } + + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions + { + LimitToEndpoint = true + }; + // D - Disabled endpoint discovery, N - None. The user did not configure anything + string userAgentContentToValidate = "|DN|"; + await this.ValidateUserAgentStringAsync( + cosmosClientOptions, + userAgentContentToValidate, + databaseName, + containerName); + } + + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions + { + ApplicationRegion = Regions.EastUS + }; + + // S - Single application region is set + string userAgentContentToValidate = "|S|"; + await this.ValidateUserAgentStringAsync( + cosmosClientOptions, + userAgentContentToValidate, + databaseName, + containerName); + } + + { + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions + { + LimitToEndpoint = false, + ApplicationRegion = null, + ApplicationPreferredRegions = new List() + { + Regions.EastUS, + Regions.WestUS + } + }; + + // L - List of region is set + string userAgentContentToValidate = "|L|"; + await this.ValidateUserAgentStringAsync( + cosmosClientOptions, + userAgentContentToValidate, + databaseName, + containerName); + } + + using (CosmosClient client = TestCommon.CreateCosmosClient()) + { + await client.GetDatabase(databaseName).DeleteStreamAsync(); + } + } + + private async Task ValidateUserAgentStringAsync( + CosmosClientOptions cosmosClientOptions, + string userAgentContentToValidate, + string databaseName, + string containerName) + { + HttpClientHandlerHelper httpClientHandlerHelper = new HttpClientHandlerHelper() + { + RequestCallBack = (request, cancellationToken) => + { + string userAgent = request.Headers.UserAgent.ToString(); + Assert.IsTrue(userAgent.Contains(userAgentContentToValidate)); + return null; + } + }; + + cosmosClientOptions.HttpClientFactory = () => new HttpClient(httpClientHandlerHelper); + + using (CosmosClient client = TestCommon.CreateCosmosClient(cosmosClientOptions)) + { + Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync(databaseName); + await db.CreateContainerIfNotExistsAsync(containerName, "/pk"); + } + } + [TestMethod] [DataRow(true, true)] [DataRow(true, false)] @@ -174,7 +273,7 @@ internal override ConnectionPolicy GetConnectionPolicy() { ConnectionPolicy connectionPolicy = base.GetConnectionPolicy(); MacOsUserAgentContainer userAgent = new MacOsUserAgentContainer(); - + this.SetUserAgentFeatures(userAgent); connectionPolicy.UserAgentContainer = userAgent; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs index 2ae461e193..ab4027925a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/BaseCosmosClientHelper.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Fluent; public abstract class BaseCosmosClientHelper { @@ -15,12 +16,14 @@ public abstract class BaseCosmosClientHelper protected CancellationTokenSource cancellationTokenSource = null; protected CancellationToken cancellationToken; - public async Task TestInit(bool validateSinglePartitionKeyRangeCacheCall = false) + public async Task TestInit( + bool validateSinglePartitionKeyRangeCacheCall = false, + Action customizeClientBuilder = null) { this.cancellationTokenSource = new CancellationTokenSource(); this.cancellationToken = this.cancellationTokenSource.Token; - this.cosmosClient = TestCommon.CreateCosmosClient(validatePartitionKeyRangeCalls: validateSinglePartitionKeyRangeCacheCall); + this.cosmosClient = TestCommon.CreateCosmosClient(validatePartitionKeyRangeCalls: validateSinglePartitionKeyRangeCacheCall, customizeClientBuilder: customizeClientBuilder); this.database = await this.cosmosClient.CreateDatabaseAsync(Guid.NewGuid().ToString(), cancellationToken: this.cancellationToken); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/QueryOracleUtil.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/QueryOracleUtil.cs index 7f69e8f017..4f77603358 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/QueryOracleUtil.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/QueryOracleUtil.cs @@ -463,7 +463,7 @@ public Query(string strFilter, string rootName = "r", FieldComparer comparer = n if (filterBuffer.Length > 0) this.query += " WHERE " + filterBuffer.ToString(); - if (comparer != null && comparer.field != null && comparer.order != 0) + if (comparer?.field != null && comparer.order != 0) { this.query += " ORDER BY r." + comparer.field.Name + (comparer.order > 0 ? " ASC" : " DESC"); this.Comparer = comparer; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/Util.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/Util.cs index 822df85b03..b17a575223 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/Util.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/Util.cs @@ -120,10 +120,7 @@ internal static void TestForEachClient( } } - if (requestChargeHelper != null) - { - requestChargeHelper.CompareRequestCharge(testName); - } + requestChargeHelper?.CompareRequestCharge(testName); } /// @@ -162,10 +159,7 @@ internal static void TestForAnyClient( client.Dispose(); } - if (requestChargeHelper != null) - { - requestChargeHelper.CompareRequestCharge(testName); - } + requestChargeHelper?.CompareRequestCharge(testName); } private static void RunTestForClient( 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 2cc4bc53f6..391337ac99 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 @@ -31,9 +31,10 @@ public class MockedItemBenchmarkHelper /// public MockedItemBenchmarkHelper( bool useCustomSerializer = false, - bool includeDiagnosticsToString = false) + bool includeDiagnosticsToString = false, + bool useBulk = false) { - this.TestClient = MockDocumentClient.CreateMockCosmosClient(useCustomSerializer); + this.TestClient = MockDocumentClient.CreateMockCosmosClient(useCustomSerializer, (builder) => builder.WithBulkExecution(useBulk)); this.TestContainer = this.TestClient.GetDatabase("myDB").GetContainer("myColl"); this.IncludeDiagnosticsToString = includeDiagnosticsToString; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/IItemBulkBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/IItemBulkBenchmark.cs new file mode 100644 index 0000000000..68a8d72d9c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/IItemBulkBenchmark.cs @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------- + +namespace Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks +{ + using System.Threading.Tasks; + + public interface IItemBulkBenchmark + { + public Task CreateItem(); + + public Task UpsertItem(); + + public Task ReadItem(); + + public Task UpdateItem(); + + public Task DeleteItem(); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs new file mode 100644 index 0000000000..4637e1fa28 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemBulkBenchmark.cs @@ -0,0 +1,65 @@ +// ---------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------- + +namespace Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks +{ + using System.Threading.Tasks; + using BenchmarkDotNet.Attributes; + + [Config(typeof(SdkBenchmarkConfiguration))] + public class MockedItemBulkBenchmark : IItemBulkBenchmark + { + public static readonly IItemBulkBenchmark[] IterParameters = new IItemBulkBenchmark[] + { + new MockedItemStreamBulkBenchmark(), + new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useBulk: true) }, + new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: true, useBulk: true) }, + new MockedItemOfTBulkBenchmark() { BenchmarkHelper = new MockedItemBenchmarkHelper(useCustomSerializer: false, includeDiagnosticsToString: true, useBulk: true) }, + }; + + [Params(ScenarioType.Stream, ScenarioType.OfT, ScenarioType.OfTWithDiagnosticsToString, ScenarioType.OfTCustom)] + public ScenarioType Type + { + get; + set; + } + + private IItemBulkBenchmark CurrentBenchmark => MockedItemBulkBenchmark.IterParameters[(int)this.Type]; + + [Benchmark] + [BenchmarkCategory("GateBenchmark")] + public async Task CreateItem() + { + await this.CurrentBenchmark.CreateItem(); + } + + [Benchmark] + [BenchmarkCategory("GateBenchmark")] + public async Task DeleteItem() + { + await this.CurrentBenchmark.DeleteItem(); + } + + [Benchmark] + [BenchmarkCategory("GateBenchmark")] + public async Task ReadItem() + { + await this.CurrentBenchmark.ReadItem(); + } + + [Benchmark] + [BenchmarkCategory("GateBenchmark")] + public async Task UpdateItem() + { + await this.CurrentBenchmark.UpdateItem(); + } + + [Benchmark] + [BenchmarkCategory("GateBenchmark")] + public async Task UpsertItem() + { + await this.CurrentBenchmark.UpsertItem(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemOfTBulkBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemOfTBulkBenchmark.cs new file mode 100644 index 0000000000..5c3cd341b2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemOfTBulkBenchmark.cs @@ -0,0 +1,86 @@ +// ---------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------- + +namespace Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks +{ + using System; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos; + + public class MockedItemOfTBulkBenchmark : IItemBulkBenchmark + { + public MockedItemBenchmarkHelper BenchmarkHelper { get; set; } + + public async Task CreateItem() + { + ItemResponse response = await this.BenchmarkHelper.TestContainer.CreateItemAsync( + this.BenchmarkHelper.TestItem, + MockedItemBenchmarkHelper.ExistingPartitionId); + + if ((int)response.StatusCode > 300 || response.Resource == null) + { + throw new Exception(); + } + + this.BenchmarkHelper.IncludeDiagnosticToStringHelper(response.Diagnostics); + } + + public async Task UpsertItem() + { + ItemResponse response = await this.BenchmarkHelper.TestContainer.UpsertItemAsync( + this.BenchmarkHelper.TestItem, + MockedItemBenchmarkHelper.ExistingPartitionId); + + if ((int)response.StatusCode > 300 || response.Resource == null) + { + throw new Exception(); + } + + this.BenchmarkHelper.IncludeDiagnosticToStringHelper(response.Diagnostics); + } + + public async Task ReadItem() + { + ItemResponse response = await this.BenchmarkHelper.TestContainer.ReadItemAsync( + MockedItemBenchmarkHelper.ExistingItemId, + MockedItemBenchmarkHelper.ExistingPartitionId); + + if ((int)response.StatusCode > 300 || response.Resource == null) + { + throw new Exception(); + } + + this.BenchmarkHelper.IncludeDiagnosticToStringHelper(response.Diagnostics); + } + + public async Task UpdateItem() + { + ItemResponse response = await this.BenchmarkHelper.TestContainer.ReplaceItemAsync( + this.BenchmarkHelper.TestItem, + MockedItemBenchmarkHelper.ExistingItemId, + MockedItemBenchmarkHelper.ExistingPartitionId); + + if ((int)response.StatusCode > 300 || response.Resource == null) + { + throw new Exception(); + } + + this.BenchmarkHelper.IncludeDiagnosticToStringHelper(response.Diagnostics); + } + + public async Task DeleteItem() + { + ItemResponse response = await this.BenchmarkHelper.TestContainer.DeleteItemAsync( + MockedItemBenchmarkHelper.ExistingItemId, + MockedItemBenchmarkHelper.ExistingPartitionId); + + if ((int)response.StatusCode > 300 || response.Resource == null) + { + throw new Exception(); + } + + this.BenchmarkHelper.IncludeDiagnosticToStringHelper(response.Diagnostics); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemStreamBulkBenchmark.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemStreamBulkBenchmark.cs new file mode 100644 index 0000000000..716c73562b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Bulk/MockedItemStreamBulkBenchmark.cs @@ -0,0 +1,109 @@ +// ---------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------- + +namespace Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks +{ + using System; + using System.IO; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos; + + public class MockedItemStreamBulkBenchmark : IItemBulkBenchmark + { + private readonly MockedItemBenchmarkHelper benchmarkHelper; + + public MockedItemStreamBulkBenchmark() + { + this.benchmarkHelper = new MockedItemBenchmarkHelper(useBulk: true); + } + + public async Task CreateItem() + { + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.benchmarkHelper.TestContainer.CreateItemStreamAsync( + ms, + new Cosmos.PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if ((int)response.StatusCode > 300 || response.Content == null) + { + throw new Exception(); + } + } + } + + public async Task UpsertItem() + { + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.benchmarkHelper.TestContainer.UpsertItemStreamAsync( + ms, + new Cosmos.PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if ((int)response.StatusCode > 300 || response.Content == null) + { + throw new Exception(); + } + } + } + + public async Task ReadItem() + { + using (ResponseMessage response = await this.benchmarkHelper.TestContainer.ReadItemStreamAsync( + MockedItemBenchmarkHelper.ExistingItemId, + new Cosmos.PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if (response.StatusCode == System.Net.HttpStatusCode.NotFound || response.Content == null) + { + throw new Exception(); + } + } + } + + public async Task ReadItemWithDiagnosticToString() + { + using (ResponseMessage response = await this.benchmarkHelper.TestContainer.ReadItemStreamAsync( + MockedItemBenchmarkHelper.ExistingItemId, + new Cosmos.PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if (response.StatusCode == System.Net.HttpStatusCode.NotFound || response.Content == null) + { + throw new Exception(); + } + + string diagnostics = response.Diagnostics.ToString(); + if (string.IsNullOrEmpty(diagnostics)) + { + throw new Exception(); + } + } + } + + public async Task UpdateItem() + { + using (MemoryStream ms = this.benchmarkHelper.GetItemPayloadAsStream()) + using (ResponseMessage response = await this.benchmarkHelper.TestContainer.ReplaceItemStreamAsync( + ms, + MockedItemBenchmarkHelper.ExistingItemId, + new Cosmos.PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if (response.StatusCode == System.Net.HttpStatusCode.NotFound || response.Content == null) + { + throw new Exception(); + } + } + } + + public async Task DeleteItem() + { + using (ResponseMessage response = await this.benchmarkHelper.TestContainer.DeleteItemStreamAsync( + MockedItemBenchmarkHelper.ExistingItemId, + new Cosmos.PartitionKey(MockedItemBenchmarkHelper.ExistingItemId))) + { + if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + throw new Exception(); + } + } + } + } +} 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 594fccf00a..196333d187 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 @@ -19,7 +19,7 @@ "MockedItemBenchmark.ReadFeed;[Type=Stream]": 42546.0, "MockedItemBenchmark.ReadItemExists;[Type=OfT]": 41814.0, "MockedItemBenchmark.ReadItemExists;[Type=OfTCustom]": 41806.0, - "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDiagnosticsToString]": 70752.0, + "MockedItemBenchmark.ReadItemExists;[Type=OfTWithDiagnosticsToString]": 74898.0, "MockedItemBenchmark.ReadItemExists;[Type=Stream]": 34464.0, "MockedItemBenchmark.ReadItemNotExists;[Type=OfT]": 52826.0, "MockedItemBenchmark.ReadItemNotExists;[Type=OfTCustom]": 52864.0, @@ -32,5 +32,25 @@ "MockedItemBenchmark.UpsertItem;[Type=OfT]": 45448.0, "MockedItemBenchmark.UpsertItem;[Type=OfTCustom]": 45458.0, "MockedItemBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": 79856.0, - "MockedItemBenchmark.UpsertItem;[Type=Stream]": 29934.0 + "MockedItemBenchmark.UpsertItem;[Type=Stream]": 29934.0, + "MockedItemBulkBenchmark.CreateItem;[Type=OfT]": 1196168.0, + "MockedItemBulkBenchmark.CreateItem;[Type=OfTCustom]": 1195808.0, + "MockedItemBulkBenchmark.CreateItem;[Type=OfTWithDiagnosticsToString]": 1231120.0, + "MockedItemBulkBenchmark.CreateItem;[Type=Stream]": 772810.0, + "MockedItemBulkBenchmark.DeleteItem;[Type=OfT]": 1187168.0, + "MockedItemBulkBenchmark.DeleteItem;[Type=OfTCustom]": 1187224.0, + "MockedItemBulkBenchmark.DeleteItem;[Type=OfTWithDiagnosticsToString]": 1222124.0, + "MockedItemBulkBenchmark.DeleteItem;[Type=Stream]": 771414.0, + "MockedItemBulkBenchmark.ReadItem;[Type=OfT]": 1186594.0, + "MockedItemBulkBenchmark.ReadItem;[Type=OfTCustom]": 1187192.0, + "MockedItemBulkBenchmark.ReadItem;[Type=OfTWithDiagnosticsToString]": 1222342.0, + "MockedItemBulkBenchmark.ReadItem;[Type=Stream]": 770894.0, + "MockedItemBulkBenchmark.UpdateItem;[Type=OfT]": 1196464.0, + "MockedItemBulkBenchmark.UpdateItem;[Type=OfTCustom]": 1195778.0, + "MockedItemBulkBenchmark.UpdateItem;[Type=OfTWithDiagnosticsToString]": 1231486.0, + "MockedItemBulkBenchmark.UpdateItem;[Type=Stream]": 773284.0, + "MockedItemBulkBenchmark.UpsertItem;[Type=OfT]": 1196210.0, + "MockedItemBulkBenchmark.UpsertItem;[Type=OfTCustom]": 1195590.0, + "MockedItemBulkBenchmark.UpsertItem;[Type=OfTWithDiagnosticsToString]": 1231380.0, + "MockedItemBulkBenchmark.UpsertItem;[Type=Stream]": 773312.0 } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/PerformanceValidation.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/PerformanceValidation.cs index 75407e9f6b..131c7ca251 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/PerformanceValidation.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Contracts/PerformanceValidation.cs @@ -72,40 +72,60 @@ public static int ValidateSummaryResultsAgainstBaseline(SortedDictionary baselineBenchmarkResults = JsonConvert.DeserializeObject>(baselineJson); - if (baselineBenchmarkResults.Count != operationToMemoryAllocated.Count) - { - Console.WriteLine(PerformanceValidation.UpdateMessage + currentBenchmarkResults); - return 1; - } - - foreach(KeyValuePair currentResult in operationToMemoryAllocated) + List failures = new List(); + SortedDictionary updatedBaseline = new SortedDictionary(); + foreach (KeyValuePair currentResult in operationToMemoryAllocated) { - double baselineResult = baselineBenchmarkResults[currentResult.Key]; + if(!baselineBenchmarkResults.TryGetValue( + currentResult.Key, + out double baselineResult)) + { + updatedBaseline.Add(currentResult.Key, currentResult.Value); + continue; + } // Add 5% buffer to avoid minor variation between test runs - double diff = currentResult.Value - baselineResult; + double diff = Math.Abs(currentResult.Value - baselineResult); double maxAllowedDiff = baselineResult * .05; + double minDiffToUpdatebaseLine = baselineResult * .02; if (diff > maxAllowedDiff) { - Console.WriteLine(PerformanceValidation.UpdateMessage + currentBenchmarkResults); - return 1; + updatedBaseline.Add(currentResult.Key, currentResult.Value); + failures.Add($"{currentResult.Key}: {currentResult.Value}"); } - else if (-diff > maxAllowedDiff) + else if(diff > minDiffToUpdatebaseLine) { - Console.WriteLine(PerformanceValidation.UpdateMessage + currentBenchmarkResults); - return 1; + // Update the value if it is greater than 2% difference. + // This reduces the noise and make it easier to see which values actually changed + updatedBaseline.Add(currentResult.Key, currentResult.Value); + } + else + { + // Use the baseline if the value didn't change by more than 2% to avoid updating values unnecessarily + // This makes it easier to see which values actually need to be updated. + updatedBaseline.Add(currentResult.Key, baselineResult); } } + // Always write the updated version. This will change with each run. + string currentBenchmarkResults = JsonConvert.SerializeObject(updatedBaseline, Formatting.Indented); + File.WriteAllText(currentDirectory + PerformanceValidation.CurrentBenchmarkResultsFileName, currentBenchmarkResults); Console.WriteLine("Current benchmark results: " + currentBenchmarkResults); + if (failures.Any()) + { + Console.WriteLine(PerformanceValidation.UpdateMessage); + foreach(string failure in failures) + { + Console.WriteLine(failure); + } + + return 1; + } + return 0; } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/BatchResponsePayloadWriter.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/BatchResponsePayloadWriter.cs new file mode 100644 index 0000000000..fca61a8e42 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/BatchResponsePayloadWriter.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Performance.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Serialization.HybridRow; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.IO; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.Layouts; + using Microsoft.Azure.Cosmos.Serialization.HybridRow.RecordIO; + using Microsoft.Azure.Documents; + + internal class BatchResponsePayloadWriter + { + private readonly List results; + private byte[] record; + + public BatchResponsePayloadWriter(List results) + { + this.results = results; + } + + internal async Task PrepareAsync() + { + MemoryStream responseStream = new MemoryStream(); + await responseStream.WriteRecordIOAsync(default, this.WriteOperationResult); + responseStream.Position = 0; + this.record = responseStream.GetBuffer(); + } + + internal MemoryStream GeneratePayload() + { + return new MemoryStream(this.record, 0, this.record.Length, writable: false, publiclyVisible: true); + } + + private Result WriteOperationResult(long index, out ReadOnlyMemory buffer) + { + if (index >= this.results.Count) + { + buffer = ReadOnlyMemory.Empty; + return Result.Success; + } + + RowBuffer row = new RowBuffer(2 * 1024); + row.InitLayout(HybridRowVersion.V1, BatchSchemaProvider.BatchResultLayout, BatchSchemaProvider.BatchLayoutResolver); + Result r = RowWriter.WriteBuffer(ref row, this.results[(int)index], BatchResponsePayloadWriter.WriteResult); + if (r != Result.Success) + { + buffer = null; + return r; + } + + MemoryStream output = new MemoryStream(row.Length); + row.WriteTo(output); + buffer = new Memory(output.GetBuffer(), 0, (int)output.Length); + return r; + } + + private static Result WriteResult(ref RowWriter writer, TypeArgument typeArg, TransactionalBatchOperationResult result) + { + Result r = writer.WriteInt32("statusCode", (int)result.StatusCode); + if (r != Result.Success) + { + return r; + } + + if (result.SubStatusCode != SubStatusCodes.Unknown) + { + r = writer.WriteInt32("subStatusCode", (int)result.SubStatusCode); + if (r != Result.Success) + { + return r; + } + } + + if (result.ETag != null) + { + r = writer.WriteString("eTag", result.ETag); + if (r != Result.Success) + { + return r; + } + } + + if (result.ResourceStream != null) + { + r = writer.WriteBinary("resourceBody", BatchResponsePayloadWriter.StreamToBytes(result.ResourceStream)); + if (r != Result.Success) + { + return r; + } + } + + if (result.RetryAfter != null) + { + r = writer.WriteUInt32("retryAfterMilliseconds", (uint)result.RetryAfter.TotalMilliseconds); + if (r != Result.Success) + { + return r; + } + } + + r = writer.WriteFloat64("requestCharge", result.RequestCharge); + if (r != Result.Success) + { + return r; + } + + return Result.Success; + } + + private static byte[] StreamToBytes(Stream stream) + { + byte[] bytes = new byte[stream.Length]; + stream.Read(bytes, 0, bytes.Length); + return bytes; + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs index aaa8abd2f1..21bcf16c6d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/Mocks/MockDocumentClient.cs @@ -161,6 +161,13 @@ private void Init() It.IsAny(), It.IsAny())).Returns(Task.FromResult(containerProperties)); + CollectionRoutingMap routingMap = CollectionRoutingMap.TryCreateCompleteRoutingMap( + new[] + { + Tuple.Create(new PartitionKeyRange{ Id = "0", MinInclusive = "", MaxExclusive = "FF"}, (ServiceIdentity)null) + }, + string.Empty); + this.partitionKeyRangeCache = new Mock(null, null, null); this.partitionKeyRangeCache.Setup( m => m.TryLookupAsync( @@ -170,7 +177,7 @@ private void Init() It.IsAny(), It.IsAny() ) - ).Returns(Task.FromResult(null)); + ).Returns(Task.FromResult(routingMap)); List result = new List { 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 da8f801920..4b0379eb38 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 @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Performance.Tests { using System; + using System.Collections.Generic; using System.IO; using Microsoft.Azure.Cosmos.Performance.Tests.Benchmarks; using Microsoft.Azure.Documents; @@ -14,6 +15,7 @@ internal static class MockRequestHelper { internal static readonly byte[] testItemResponsePayload; internal static readonly byte[] testItemFeedResponsePayload; + internal static readonly BatchResponsePayloadWriter batchResponsePayloadWriter; static MockRequestHelper() { @@ -30,6 +32,18 @@ static MockRequestHelper() fs.CopyTo(ms); MockRequestHelper.testItemFeedResponsePayload = ms.ToArray(); } + + List results = new List + { + new TransactionalBatchOperationResult(System.Net.HttpStatusCode.OK) + { + ResourceStream = new MemoryStream(MockRequestHelper.testItemFeedResponsePayload, 0, MockRequestHelper.testItemFeedResponsePayload.Length, writable: false, publiclyVisible: true), + ETag = Guid.NewGuid().ToString() + } + }; + + batchResponsePayloadWriter = new BatchResponsePayloadWriter(results); + batchResponsePayloadWriter.PrepareAsync().GetAwaiter().GetResult(); } /// @@ -177,6 +191,18 @@ public static StoreResponse GetStoreResponse(DocumentServiceRequest request) }; } + if (request.OperationType == OperationType.Batch) + { + MemoryStream responseContent = batchResponsePayloadWriter.GeneratePayload(); + + return new StoreResponse() + { + ResponseBody = responseContent, + Status = (int)System.Net.HttpStatusCode.OK, + Headers = headers, + }; + } + return null; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.Binary.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.Binary.xml index e1c13f3a92..d3c9d79fa7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.Binary.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/ScalarExpressionSqlParserBaselineTests.Binary.xml @@ -302,7 +302,79 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.ScenariosAsync.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.ScenariosAsync.xml index 718b85458f..b43a6f2051 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.ScenariosAsync.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BaselineTest/TestBaseline/TraceWriterBaselineTests.ScenariosAsync.xml @@ -1436,7 +1436,7 @@ │ └── Change Feed Transport(00000000-0000-0000-0000-000000000000) Transport-Component MemberName@FilePath:42 12:00:00:000 0.00 milliseconds └── MoveNextAsync(00000000-0000-0000-0000-000000000000) Pagination-Component MemberName@FilePath:42 12:00:00:000 0.00 milliseconds └── [9F-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF,BF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF) move next(00000000-0000-0000-0000-000000000000) Pagination-Component MemberName@FilePath:42 12:00:00:000 0.00 milliseconds - └── Change Feed Transport(00000000-0000-0000-0000-000000000000) Transport-Component MemberName@FilePath:42 12:00:00:000 0.00 milliseconds + └── Change Feed Transport(00000000-0000-0000-0000-000000000000) Transport-Component MemberName@FilePath:42 12:00:00:000 0.00 milliseconds ]]> + { + List results = new List(); + ItemBatchOperation[] arrayOperations = new ItemBatchOperation[request.Operations.Count]; + int index = 0; + foreach (ItemBatchOperation operation in request.Operations) + { + if (index == 0) + { + // First operation is fine + results.Add( + new TransactionalBatchOperationResult(HttpStatusCode.OK) + { + ETag = operation.Id + }); + } + else + { + // second operation is too big + results.Add( + new TransactionalBatchOperationResult(HttpStatusCode.RequestEntityTooLarge) + { + SubStatusCode = (SubStatusCodes)3402, + ETag = operation.Id + }); + } + + arrayOperations[index++] = operation; + } + + MemoryStream responseContent = await new BatchResponsePayloadWriter(results).GeneratePayloadAsync(); + + SinglePartitionKeyServerBatchRequest batchRequest = await SinglePartitionKeyServerBatchRequest.CreateAsync( + partitionKey: null, + operations: new ArraySegment(arrayOperations), + serializerCore: MockCosmosUtil.Serializer, + trace: trace, + cancellationToken: cancellationToken); + + ResponseMessage responseMessage = new ResponseMessage((HttpStatusCode)207) + { + Content = responseContent + }; + + TransactionalBatchResponse batchresponse = await TransactionalBatchResponse.FromResponseMessageAsync( + responseMessage, + batchRequest, + MockCosmosUtil.Serializer, + true, + trace: trace, + CancellationToken.None); + + return new PartitionKeyRangeBatchExecutionResult(request.PartitionKeyRangeId, request.Operations, batchresponse); + }; + // The response will include all but 2 operation responses private readonly BatchAsyncBatcherExecuteDelegate ExecutorWithLessResponses = async (PartitionKeyRangeServerBatchRequest request, ITrace trace, CancellationToken cancellationToken) => @@ -310,7 +366,7 @@ private readonly BatchAsyncBatcherExecuteDelegate ExecutorWithLessResponses private readonly BatchAsyncBatcherExecuteDelegate ExecutorWithFailure = (PartitionKeyRangeServerBatchRequest request, ITrace trace, CancellationToken cancellationToken) => throw expectedException; - private readonly BatchAsyncBatcherRetryDelegate Retrier = (ItemBatchOperation operation, ITrace trace, CancellationToken cancellation) => Task.CompletedTask; + private readonly BatchAsyncBatcherRetryDelegate Retrier = (ItemBatchOperation operation, CancellationToken cancellation) => Task.CompletedTask; [DataTestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] @@ -378,9 +434,9 @@ public async Task ExceptionsFailOperationsAsync() BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(2, 1000, MockCosmosUtil.Serializer, this.ExecutorWithFailure, this.Retrier, BatchAsyncBatcherTests.MockClientContext()); ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - ItemBatchOperationContext context1 = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext context1 = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation1.AttachContext(context1); - ItemBatchOperationContext context2 = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext context2 = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation2.AttachContext(context2); batchAsyncBatcher.TryAdd(operation1); batchAsyncBatcher.TryAdd(operation2); @@ -405,7 +461,7 @@ public async Task DispatchProcessInOrderAsync() partitionKey: new Cosmos.PartitionKey(i.ToString()), id: i.ToString()); - ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation.AttachContext(context); operations.Add(operation); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation)); @@ -431,7 +487,7 @@ public async Task DispatchWithLessResponses() for (int i = 0; i < 10; i++) { ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, i, Cosmos.PartitionKey.Null, i.ToString()); - ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation.AttachContext(context); operations.Add(operation); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation)); @@ -496,7 +552,7 @@ public async Task CannotAddToDispatchedBatch() { BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(1, 1000, MockCosmosUtil.Serializer, this.Executor, this.Retrier, BatchAsyncBatcherTests.MockClientContext()); ItemBatchOperation operation = this.CreateItemBatchOperation(); - operation.AttachContext(new ItemBatchOperationContext(string.Empty)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation)); await batchAsyncBatcher.DispatchAsync(metric); Assert.IsFalse(batchAsyncBatcher.TryAdd(this.CreateItemBatchOperation())); @@ -517,8 +573,8 @@ public async Task RetrierGetsCalledOnSplit() ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - operation1.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy1)); - operation2.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy2)); + operation1.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy1)); + operation2.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy2)); Mock retryDelegate = new Mock(); @@ -526,9 +582,9 @@ public async Task RetrierGetsCalledOnSplit() Assert.IsTrue(batchAsyncBatcher.TryAdd(operation1)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation2)); await batchAsyncBatcher.DispatchAsync(metric); - retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + 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)); } [TestMethod] @@ -546,8 +602,8 @@ public async Task RetrierGetsCalledOnCompletingSplit() ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - operation1.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy1)); - operation2.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy2)); + operation1.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy1)); + operation2.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy2)); Mock retryDelegate = new Mock(); @@ -555,9 +611,9 @@ public async Task RetrierGetsCalledOnCompletingSplit() Assert.IsTrue(batchAsyncBatcher.TryAdd(operation1)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation2)); await batchAsyncBatcher.DispatchAsync(metric); - retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + 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)); } [TestMethod] @@ -575,8 +631,8 @@ public async Task RetrierGetsCalledOnCompletingPartitionMigration() ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - operation1.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy1)); - operation2.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy2)); + operation1.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy1)); + operation2.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy2)); Mock retryDelegate = new Mock(); @@ -584,9 +640,9 @@ public async Task RetrierGetsCalledOnCompletingPartitionMigration() Assert.IsTrue(batchAsyncBatcher.TryAdd(operation1)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation2)); await batchAsyncBatcher.DispatchAsync(metric); - retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); + 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)); } [TestMethod] @@ -594,8 +650,8 @@ public async Task RetrierGetsCalledOnOverFlow() { ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - operation1.AttachContext(new ItemBatchOperationContext(string.Empty)); - operation2.AttachContext(new ItemBatchOperationContext(string.Empty)); + operation1.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton)); + operation2.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton)); Mock retryDelegate = new Mock(); Mock executeDelegate = new Mock(); @@ -604,13 +660,13 @@ public async Task RetrierGetsCalledOnOverFlow() Assert.IsTrue(batchAsyncBatcher.TryAdd(operation1)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation2)); await batchAsyncBatcher.DispatchAsync(metric); - retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny(), It.IsAny()), Times.Never); - retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny()), Times.Never); + retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny()), Times.Once); + retryDelegate.Verify(a => a(It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] - public async Task RetrierGetsCalledOn413_OnRead() + public async Task RetrierGetsCalledOn413_3402() { IDocumentClientRetryPolicy retryPolicy1 = new BulkExecutionRetryPolicy( GetSplitEnabledContainer(), @@ -622,49 +678,67 @@ public async Task RetrierGetsCalledOn413_OnRead() OperationType.Read, new ResourceThrottleRetryPolicy(1)); + IDocumentClientRetryPolicy retryPolicy3 = new BulkExecutionRetryPolicy( + GetSplitEnabledContainer(), + OperationType.Create, + new ResourceThrottleRetryPolicy(1)); + ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - operation1.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy1)); - operation2.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy2)); + ItemBatchOperation operation3 = this.CreateItemBatchOperation(); + operation1.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy1)); + operation2.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy2)); + operation3.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy3)); Mock retryDelegate = new Mock(); - BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(2, 1000, MockCosmosUtil.Serializer, this.ExecutorWith413, retryDelegate.Object, BatchAsyncBatcherTests.MockClientContext()); + BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(3, 1000, MockCosmosUtil.Serializer, this.ExecutorWith413_3402, retryDelegate.Object, BatchAsyncBatcherTests.MockClientContext()); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation1)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation2)); + Assert.IsTrue(batchAsyncBatcher.TryAdd(operation3)); await batchAsyncBatcher.DispatchAsync(metric); - retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny(), It.IsAny()), Times.Never); - retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny(), It.IsAny()), Times.Once); - retryDelegate.Verify(a => a(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny()), Times.Never); + retryDelegate.Verify(a => a(It.Is(o => o == operation2), 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)); } [TestMethod] - public async Task RetrierGetsCalledOn413_OnWrite() + public async Task RetrierGetsCalledOn413_NoSubstatus() { IDocumentClientRetryPolicy retryPolicy1 = new BulkExecutionRetryPolicy( GetSplitEnabledContainer(), - OperationType.Create, + OperationType.Read, new ResourceThrottleRetryPolicy(1)); IDocumentClientRetryPolicy retryPolicy2 = new BulkExecutionRetryPolicy( + GetSplitEnabledContainer(), + OperationType.Read, + new ResourceThrottleRetryPolicy(1)); + + IDocumentClientRetryPolicy retryPolicy3 = new BulkExecutionRetryPolicy( GetSplitEnabledContainer(), OperationType.Create, new ResourceThrottleRetryPolicy(1)); ItemBatchOperation operation1 = this.CreateItemBatchOperation(); ItemBatchOperation operation2 = this.CreateItemBatchOperation(); - operation1.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy1)); - operation2.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy2)); + ItemBatchOperation operation3 = this.CreateItemBatchOperation(); + operation1.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy1)); + operation2.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy2)); + operation3.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy3)); Mock retryDelegate = new Mock(); - BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(2, 1000, MockCosmosUtil.Serializer, this.ExecutorWith413, retryDelegate.Object, BatchAsyncBatcherTests.MockClientContext()); + BatchAsyncBatcher batchAsyncBatcher = new BatchAsyncBatcher(3, 1000, MockCosmosUtil.Serializer, this.ExecutorWith413, retryDelegate.Object, BatchAsyncBatcherTests.MockClientContext()); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation1)); Assert.IsTrue(batchAsyncBatcher.TryAdd(operation2)); + Assert.IsTrue(batchAsyncBatcher.TryAdd(operation3)); await batchAsyncBatcher.DispatchAsync(metric); - retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny(), It.IsAny()), Times.Never); - retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny(), It.IsAny()), Times.Never); - retryDelegate.Verify(a => a(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + retryDelegate.Verify(a => a(It.Is(o => o == operation1), It.IsAny()), Times.Never); + retryDelegate.Verify(a => a(It.Is(o => o == operation2), It.IsAny()), Times.Never); + retryDelegate.Verify(a => a(It.Is(o => o == operation3), It.IsAny()), Times.Never); + retryDelegate.Verify(a => a(It.IsAny(), It.IsAny()), Times.Never); } private static ContainerInternal GetSplitEnabledContainer() 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 6b9915a562..96154f65f6 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 @@ -12,7 +12,6 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Net.Http; using System.Threading; using System.Threading.Tasks; - using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Cosmos.Tracing.TraceData; @@ -50,8 +49,8 @@ public async Task RetryOnSplit() string link = "/dbs/db/colls/colls"; Mock mockContainer = new Mock(); mockContainer.Setup(x => x.LinkUri).Returns(link); - mockContainer.Setup(x => x.GetPartitionKeyDefinitionAsync(It.IsAny())).Returns(Task.FromResult(new PartitionKeyDefinition() { Paths = new Collection() { "/id" } })); - mockContainer.Setup(c => c.GetCachedRIDAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Guid.NewGuid().ToString()); + mockContainer.Setup(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ContainerProperties() { PartitionKey = new PartitionKeyDefinition() { Paths = new Collection() { "/id" } } })); Mock context = this.MockClientContext(); mockContainer.Setup(c => c.ClientContext).Returns(context.Object); context.Setup(c => c.DocumentClient).Returns(new ClientWithSplitDetection()); @@ -65,10 +64,10 @@ public async Task RetryOnSplit() string.Empty); mockContainer.Setup(x => x.GetRoutingMapAsync(It.IsAny())).Returns(Task.FromResult(routingMap)); BatchAsyncContainerExecutor executor = new BatchAsyncContainerExecutor(mockContainer.Object, mockedContext.Object, 20, BatchAsyncContainerExecutorCache.DefaultMaxBulkRequestBodySizeInBytes); - TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation); + TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation, NoOpTrace.Singleton); Mock.Get(mockContainer.Object) - .Verify(x => x.GetPartitionKeyDefinitionAsync(It.IsAny()), Times.Exactly(2)); + .Verify(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); Mock.Get(mockedContext.Object) .Verify(c => c.ProcessResourceOperationStreamAsync( It.IsAny(), @@ -112,7 +111,8 @@ public async Task RetryOnNameStale() string link = "/dbs/db/colls/colls"; Mock mockContainer = new Mock(); mockContainer.Setup(x => x.LinkUri).Returns(link); - mockContainer.Setup(x => x.GetPartitionKeyDefinitionAsync(It.IsAny())).Returns(Task.FromResult(new PartitionKeyDefinition() { Paths = new Collection() { "/id" } })); + mockContainer.Setup(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ContainerProperties() { PartitionKey = new PartitionKeyDefinition() { Paths = new Collection() { "/id" } } })); CollectionRoutingMap routingMap = CollectionRoutingMap.TryCreateCompleteRoutingMap( new[] @@ -122,10 +122,10 @@ public async Task RetryOnNameStale() string.Empty); mockContainer.Setup(x => x.GetRoutingMapAsync(It.IsAny())).Returns(Task.FromResult(routingMap)); BatchAsyncContainerExecutor executor = new BatchAsyncContainerExecutor(mockContainer.Object, mockedContext.Object, 20, BatchAsyncContainerExecutorCache.DefaultMaxBulkRequestBodySizeInBytes); - TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation); + TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation, NoOpTrace.Singleton); Mock.Get(mockContainer.Object) - .Verify(x => x.GetPartitionKeyDefinitionAsync(It.IsAny()), Times.Exactly(2)); + .Verify(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); Mock.Get(mockedContext.Object) .Verify(c => c.ProcessResourceOperationStreamAsync( It.IsAny(), @@ -169,7 +169,8 @@ public async Task RetryOn429() string link = $"/dbs/db/colls/colls"; Mock mockContainer = new Mock(); mockContainer.Setup(x => x.LinkUri).Returns(link); - mockContainer.Setup(x => x.GetPartitionKeyDefinitionAsync(It.IsAny())).Returns(Task.FromResult(new PartitionKeyDefinition() { Paths = new Collection() { "/id" } })); + mockContainer.Setup(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ContainerProperties() { PartitionKey = new PartitionKeyDefinition() { Paths = new Collection() { "/id" } } })); CollectionRoutingMap routingMap = CollectionRoutingMap.TryCreateCompleteRoutingMap( new[] @@ -179,10 +180,10 @@ public async Task RetryOn429() string.Empty); mockContainer.Setup(x => x.GetRoutingMapAsync(It.IsAny())).Returns(Task.FromResult(routingMap)); BatchAsyncContainerExecutor executor = new BatchAsyncContainerExecutor(mockContainer.Object, mockedContext.Object, 20, BatchAsyncContainerExecutorCache.DefaultMaxBulkRequestBodySizeInBytes); - TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation); + TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation, NoOpTrace.Singleton); Mock.Get(mockContainer.Object) - .Verify(x => x.GetPartitionKeyDefinitionAsync(It.IsAny()), Times.Exactly(2)); + .Verify(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(2)); Mock.Get(mockedContext.Object) .Verify(c => c.ProcessResourceOperationStreamAsync( It.IsAny(), @@ -225,7 +226,8 @@ public async Task DoesNotRecalculatePartitionKeyRangeOnNoSplits() string link = "/dbs/db/colls/colls"; Mock mockContainer = new Mock(); mockContainer.Setup(x => x.LinkUri).Returns(link); - mockContainer.Setup(x => x.GetPartitionKeyDefinitionAsync(It.IsAny())).Returns(Task.FromResult(new PartitionKeyDefinition() { Paths = new Collection() { "/id" } })); + mockContainer.Setup(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(new ContainerProperties() { PartitionKey = new PartitionKeyDefinition() { Paths = new Collection() { "/id" } } })); CollectionRoutingMap routingMap = CollectionRoutingMap.TryCreateCompleteRoutingMap( new[] @@ -235,10 +237,10 @@ public async Task DoesNotRecalculatePartitionKeyRangeOnNoSplits() string.Empty); mockContainer.Setup(x => x.GetRoutingMapAsync(It.IsAny())).Returns(Task.FromResult(routingMap)); BatchAsyncContainerExecutor executor = new BatchAsyncContainerExecutor(mockContainer.Object, mockedContext.Object, 20, BatchAsyncContainerExecutorCache.DefaultMaxBulkRequestBodySizeInBytes); - TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation); + TransactionalBatchOperationResult result = await executor.AddAsync(itemBatchOperation, NoOpTrace.Singleton); Mock.Get(mockContainer.Object) - .Verify(x => x.GetPartitionKeyDefinitionAsync(It.IsAny()), Times.Once); + .Verify(x => x.GetCachedContainerPropertiesAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); Mock.Get(mockedContext.Object) .Verify(c => c.ProcessResourceOperationStreamAsync( It.IsAny(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs index cc01ad6cce..5ec655d386 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncOperationContextTests.cs @@ -18,12 +18,93 @@ namespace Microsoft.Azure.Cosmos.Tests [TestClass] public class BatchAsyncOperationContextTests { + [TestMethod] + public async Task TraceIsJoinedOnCompletionWithRetry() + { + IDocumentClientRetryPolicy retryPolicy = new BulkExecutionRetryPolicy( + Mock.Of(), + OperationType.Read, + new ResourceThrottleRetryPolicy(1)); + + Trace rootTrace = Trace.GetRootTrace(name: "RootTrace"); + + ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); + + // Start with the base trace + ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(Guid.NewGuid().ToString(), rootTrace, retryPolicy); + operation.AttachContext(batchAsyncOperationContext); + + // Simulate a retry scenario that should append to the context traces + Trace retryTrace = Trace.GetRootTrace(name: "TransportTrace"); + TransactionalBatchOperationResult retryResult = new TransactionalBatchOperationResult(HttpStatusCode.TooManyRequests) + { + Trace = retryTrace + }; + ShouldRetryResult shouldRetryResult = await batchAsyncOperationContext.ShouldRetryAsync(retryResult, default); + Assert.IsTrue(shouldRetryResult.ShouldRetry); + + // Simulate the completion that should append to the context traces + Trace transportTrace = Trace.GetRootTrace(name: "TransportTrace"); + TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.OK) + { + Trace = transportTrace + }; + + batchAsyncOperationContext.Complete(null, result); + + Assert.AreEqual(result, await batchAsyncOperationContext.OperationTask); + Assert.AreEqual(2, result.Trace.Children.Count, "The final trace should have the initial trace, plus the retries, plus the final trace"); + Assert.AreEqual(rootTrace, result.Trace, "The first trace child should be the initial root"); + Assert.AreEqual(retryTrace, result.Trace.Children[0], "The second trace child should be the one from the retry"); + Assert.AreEqual(transportTrace, result.Trace.Children[1], "The third trace child should be the one from the final result"); + } + + [TestMethod] + public async Task TraceIsJoinedOnCompletionWithoutRetry() + { + IDocumentClientRetryPolicy retryPolicy = new BulkExecutionRetryPolicy( + Mock.Of(), + OperationType.Read, + new ResourceThrottleRetryPolicy(1)); + + Trace rootTrace = Trace.GetRootTrace(name: "RootTrace"); + + ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); + + // Start with the base trace + ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(Guid.NewGuid().ToString(), rootTrace, retryPolicy); + operation.AttachContext(batchAsyncOperationContext); + + // Simulate a retry scenario that should not append to the context traces + Trace retryTrace = Trace.GetRootTrace(name: "TransportTrace"); + TransactionalBatchOperationResult retryResult = new TransactionalBatchOperationResult(HttpStatusCode.Forbidden) + { + Trace = retryTrace + }; + ShouldRetryResult shouldRetryResult = await batchAsyncOperationContext.ShouldRetryAsync(retryResult, default); + Assert.IsFalse(shouldRetryResult.ShouldRetry); + + // Simulate the completion that should append to the context traces + Trace transportTrace = Trace.GetRootTrace(name: "TransportTrace"); + TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.OK) + { + Trace = transportTrace + }; + + batchAsyncOperationContext.Complete(null, result); + + Assert.AreEqual(result, await batchAsyncOperationContext.OperationTask); + Assert.AreEqual(1, result.Trace.Children.Count, "The final trace should have the initial trace, plus the final trace, since the result is not retried, it should not capture it"); + Assert.AreEqual(rootTrace, result.Trace, "The first trace child should be the initial root"); + Assert.AreEqual(transportTrace, result.Trace.Children[0], "The second trace child should be the one from the final result"); + } + [TestMethod] public void PartitionKeyRangeIdIsSetOnInitialization() { string expectedPkRangeId = Guid.NewGuid().ToString(); ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(expectedPkRangeId); + ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(expectedPkRangeId, NoOpTrace.Singleton); operation.AttachContext(batchAsyncOperationContext); Assert.IsNotNull(batchAsyncOperationContext.OperationTask); @@ -36,7 +117,7 @@ public void PartitionKeyRangeIdIsSetOnInitialization() public void TaskIsCreatedOnInitialization() { ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation.AttachContext(batchAsyncOperationContext); Assert.IsNotNull(batchAsyncOperationContext.OperationTask); @@ -48,7 +129,7 @@ public void TaskIsCreatedOnInitialization() public async Task TaskResultIsSetOnCompleteAsync() { ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation.AttachContext(batchAsyncOperationContext); TransactionalBatchOperationResult expected = new TransactionalBatchOperationResult(HttpStatusCode.OK); @@ -64,7 +145,7 @@ public async Task ExceptionIsSetOnFailAsync() { Exception failure = new Exception("It failed"); ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext batchAsyncOperationContext = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation.AttachContext(batchAsyncOperationContext); batchAsyncOperationContext.Fail(null, failure); @@ -78,8 +159,8 @@ public async Task ExceptionIsSetOnFailAsync() public void CannotAttachMoreThanOnce() { ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty)); - Assert.ThrowsException(() => operation.AttachContext(new ItemBatchOperationContext(string.Empty))); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton)); + Assert.ThrowsException(() => operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton))); } [TestMethod] @@ -87,7 +168,7 @@ public async Task ShouldRetry_NoPolicy() { TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.OK); ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsFalse(shouldRetryResult.ShouldRetry); } @@ -101,7 +182,7 @@ public async Task ShouldRetry_WithPolicy_OnSuccess() new ResourceThrottleRetryPolicy(1)); TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.OK); ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsFalse(shouldRetryResult.ShouldRetry); } @@ -115,27 +196,27 @@ public async Task ShouldRetry_WithPolicy_On429() new ResourceThrottleRetryPolicy(1)); TransactionalBatchOperationResult result = new TransactionalBatchOperationResult((HttpStatusCode)StatusCodes.TooManyRequests); ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsTrue(shouldRetryResult.ShouldRetry); } [TestMethod] - public async Task ShouldRetry_WithPolicy_On413_OnRead() + public async Task ShouldRetry_WithPolicy_On413_3402() { IDocumentClientRetryPolicy retryPolicy = new BulkExecutionRetryPolicy( Mock.Of(), OperationType.Read, new ResourceThrottleRetryPolicy(1)); - TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.RequestEntityTooLarge); + TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.RequestEntityTooLarge) { SubStatusCode = (SubStatusCodes)3402 }; ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsTrue(shouldRetryResult.ShouldRetry); } [TestMethod] - public async Task ShouldRetry_WithPolicy_On413_OnWrite() + public async Task ShouldRetry_WithPolicy_On413_0() { IDocumentClientRetryPolicy retryPolicy = new BulkExecutionRetryPolicy( Mock.Of(), @@ -143,7 +224,7 @@ public async Task ShouldRetry_WithPolicy_On413_OnWrite() new ResourceThrottleRetryPolicy(1)); TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.RequestEntityTooLarge); ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsFalse(shouldRetryResult.ShouldRetry); } @@ -157,7 +238,7 @@ public async Task ShouldRetry_WithPolicy_OnSplit() new ResourceThrottleRetryPolicy(1)); TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.Gone) { SubStatusCode = SubStatusCodes.PartitionKeyRangeGone }; ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsTrue(shouldRetryResult.ShouldRetry); } @@ -171,7 +252,7 @@ public async Task ShouldRetry_WithPolicy_OnCompletingSplit() new ResourceThrottleRetryPolicy(1)); TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.Gone) { SubStatusCode = SubStatusCodes.CompletingSplit }; ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsTrue(shouldRetryResult.ShouldRetry); } @@ -185,7 +266,7 @@ public async Task ShouldRetry_WithPolicy_OnCompletingPartitionMigration() new ResourceThrottleRetryPolicy(1)); TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.Gone) { SubStatusCode = SubStatusCodes.CompletingPartitionMigration }; ItemBatchOperation operation = new ItemBatchOperation(OperationType.Create, 0, Cosmos.PartitionKey.Null); - operation.AttachContext(new ItemBatchOperationContext(string.Empty, retryPolicy)); + operation.AttachContext(new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton, retryPolicy)); ShouldRetryResult shouldRetryResult = await operation.Context.ShouldRetryAsync(result, default); Assert.IsTrue(shouldRetryResult.ShouldRetry); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs index 00cf2bb543..408b0ce172 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchAsyncStreamerTests.cs @@ -68,7 +68,7 @@ private readonly BatchAsyncBatcherExecuteDelegate Executor private readonly BatchAsyncBatcherExecuteDelegate ExecutorWithFailure = (PartitionKeyRangeServerBatchRequest request, ITrace trace, CancellationToken cancellationToken) => throw expectedException; - private readonly BatchAsyncBatcherRetryDelegate Retrier = (ItemBatchOperation operation, ITrace trace, CancellationToken cancellation) => Task.CompletedTask; + private readonly BatchAsyncBatcherRetryDelegate Retrier = (ItemBatchOperation operation, CancellationToken cancellation) => Task.CompletedTask; [DataTestMethod] [ExpectedException(typeof(ArgumentOutOfRangeException))] @@ -190,7 +190,7 @@ public async Task DispatchesAsync() private static ItemBatchOperationContext AttachContext(ItemBatchOperation operation) { - ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty); + ItemBatchOperationContext context = new ItemBatchOperationContext(string.Empty, NoOpTrace.Singleton); operation.AttachContext(context); return context; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs index a68e79b2a1..e1c8ad0cdf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/BulkPartitionKeyRangeGoneRetryPolicyTests.cs @@ -45,20 +45,20 @@ public async Task RetriesOn429() } [TestMethod] - public async Task RetriesOn413_OnRead() + public async Task RetriesOn413_3204() { IDocumentClientRetryPolicy retryPolicy = new BulkExecutionRetryPolicy( Mock.Of(), OperationType.Read, new ResourceThrottleRetryPolicy(1)); - TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.RequestEntityTooLarge); + TransactionalBatchOperationResult result = new TransactionalBatchOperationResult(HttpStatusCode.RequestEntityTooLarge) { SubStatusCode = (SubStatusCodes)3402 }; ShouldRetryResult shouldRetryResult = await retryPolicy.ShouldRetryAsync(result.ToResponseMessage(), default); Assert.IsTrue(shouldRetryResult.ShouldRetry); } [TestMethod] - public async Task RetriesOn413_OnWrite() + public async Task RetriesOn413_0() { IDocumentClientRetryPolicy retryPolicy = new BulkExecutionRetryPolicy( Mock.Of(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CancellationTokenTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CancellationTokenTests.cs index b5d6174f48..948f7ec9b7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CancellationTokenTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CancellationTokenTests.cs @@ -52,11 +52,11 @@ async Task sendFunc(HttpRequestMessage request) Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); ISessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -171,7 +171,7 @@ StoreResponse sendDirectFunc(Uri uri, ResourceOperation resourceOperation, Docum "Should have surfaced OperationCanceledException because the token was canceled."); ((MockDocumentClient)client.DocumentClient).MockGlobalEndpointManager.Verify(gep => gep.MarkEndpointUnavailableForRead(It.IsAny()), Times.Once, "Should had marked the endpoint unavailable"); - ((MockDocumentClient)client.DocumentClient).MockGlobalEndpointManager.Verify(gep => gep.RefreshLocationAsync(It.IsAny(), It.Is(refresh => refresh == false)), Times.Once, "Should had refreshed the account information"); + ((MockDocumentClient)client.DocumentClient).MockGlobalEndpointManager.Verify(gep => gep.RefreshLocationAsync(false), Times.Once, "Should had refreshed the account information"); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/CrossPartitionChangeFeedAsyncEnumeratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/CrossPartitionChangeFeedAsyncEnumeratorTests.cs index 224157111b..8056fcf076 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/CrossPartitionChangeFeedAsyncEnumeratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/CrossPartitionChangeFeedAsyncEnumeratorTests.cs @@ -378,6 +378,52 @@ public async Task ShouldSkipNotModifiedAndReturnResults() It.IsAny()), Times.Never); } + [TestMethod] + public async Task ShouldReturnTryCatchOnException() + { + ReadOnlyMemory> rangeStates = new FeedRangeState[]{ + new FeedRangeState(FeedRangeEpk.FullRange, ChangeFeedState.Now()) + }; + + CrossFeedRangeState state = new CrossFeedRangeState(rangeStates); + Mock documentContainer = new Mock(); + + Exception exception = new Exception("oh no"); + + // Throws unhandled exception + documentContainer.Setup(c => c.MonadicChangeFeedAsync( + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())).ThrowsAsync(exception); + CrossPartitionChangeFeedAsyncEnumerator enumerator = CrossPartitionChangeFeedAsyncEnumerator.Create( + documentContainer.Object, + state, + ChangeFeedPaginationOptions.Default, + cancellationToken: default); + + try + { + await enumerator.MoveNextAsync(NoOpTrace.Singleton); + Assert.Fail("Should have thrown"); + } + catch (Exception caughtException) + { + Assert.AreEqual(exception, caughtException); + } + + // Should be able to read MoveNextAsync again + try + { + await enumerator.MoveNextAsync(NoOpTrace.Singleton); + Assert.Fail("Should have thrown"); + } + catch (Exception caughtException) + { + Assert.AreEqual(exception, caughtException); + } + } + private static async Task<(int, double)> DrainUntilNotModifedAsync(CrossPartitionChangeFeedAsyncEnumerator enumerator) { int globalCount = 0; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs index 0d7793bcf3..b56a9b377a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeed/PartitionSynchronizerCoreTests.cs @@ -37,7 +37,7 @@ public async Task HandlePartitionGoneAsync_PKRangeBasedLease_Split() }; Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); @@ -100,7 +100,7 @@ public async Task HandlePartitionGoneAsync_EpkBasedLease_Split() }; Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); @@ -168,7 +168,7 @@ public async Task HandlePartitionGoneAsync_PKRangeBasedLease_Merge() }; Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); @@ -226,7 +226,7 @@ public async Task HandlePartitionGoneAsync_EpkBasedLease_Merge() }; Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); @@ -274,7 +274,7 @@ public async Task HandlePartitionGoneAsync_EpkBasedLease_Merge() public async Task CreateMissingLeases_NoLeases() { Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); @@ -319,7 +319,7 @@ public async Task CreateMissingLeases_NoLeases() public async Task CreateMissingLeases_SomePKRangeLeases() { Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); @@ -370,7 +370,7 @@ public async Task CreateMissingLeases_SomePKRangeLeases() public async Task CreateMissingLeases_SomePKRangeAndEPKLeases() { Mock pkRangeCache = new Mock( - Mock.Of(), + Mock.Of(), Mock.Of(), Mock.Of()); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index eec6847024..4ac607fb79 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -587,6 +587,20 @@ "Microsoft.Azure.Cosmos.ClientEncryptionPolicy;System.Object;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { + "Int32 get_PolicyFormatVersion()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Int32 get_PolicyFormatVersion();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int32 PolicyFormatVersion[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"policyFormatVersion\")]": { + "Type": "Property", + "Attributes": [ + "JsonPropertyAttribute" + ], + "MethodInfo": "Int32 PolicyFormatVersion;CanRead:True;CanWrite:True;Int32 get_PolicyFormatVersion();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Collections.Generic.IEnumerable`1[Microsoft.Azure.Cosmos.ClientEncryptionIncludedPath] get_IncludedPaths()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ @@ -978,43 +992,6 @@ "Type": "Property", "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosResponseFactory ResponseFactory;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.CosmosResponseFactory get_ResponseFactory();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.CosmosClient] CreateAndInitializeAsync(System.String, Azure.Core.TokenCredential, System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.String]], Microsoft.Azure.Cosmos.CosmosClientOptions, System.Threading.CancellationToken)[System.Runtime.CompilerServices.AsyncStateMachineAttribute(typeof(Microsoft.Azure.Cosmos.CosmosClient+))]": { - "Type": "Method", - "Attributes": [ - "AsyncStateMachineAttribute" - ], - "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.CosmosClient] CreateAndInitializeAsync(System.String, Azure.Core.TokenCredential, System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.String]], Microsoft.Azure.Cosmos.CosmosClientOptions, System.Threading.CancellationToken);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void .ctor(System.String, Azure.Core.TokenCredential, Microsoft.Azure.Cosmos.CosmosClientOptions)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "[Void .ctor(System.String, Azure.Core.TokenCredential, Microsoft.Azure.Cosmos.CosmosClientOptions), Void .ctor(System.String, Azure.Core.TokenCredential, Microsoft.Azure.Cosmos.CosmosClientOptions)]" - } - }, - "NestedTypes": {} - }, - "Microsoft.Azure.Cosmos.CosmosClientOptions;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "System.Nullable`1[System.TimeSpan] get_TokenCredentialBackgroundRefreshInterval()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "System.Nullable`1[System.TimeSpan] get_TokenCredentialBackgroundRefreshInterval();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.Nullable`1[System.TimeSpan] TokenCredentialBackgroundRefreshInterval": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "System.Nullable`1[System.TimeSpan] TokenCredentialBackgroundRefreshInterval;CanRead:True;CanWrite:True;System.Nullable`1[System.TimeSpan] get_TokenCredentialBackgroundRefreshInterval();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" } }, "NestedTypes": {} @@ -1369,27 +1346,13 @@ "NestedTypes": {} }, "Microsoft.Azure.Cosmos.PatchOperation;System.Object;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { - "Subclasses": { - "Microsoft.Azure.Cosmos.PatchOperation`1;Microsoft.Azure.Cosmos.PatchOperation;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:True;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "T get_Value()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "T get_Value();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "T Value[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"value\")]": { - "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "T Value;CanRead:True;CanWrite:False;T get_Value();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - } - }, + "Subclasses": {}, "Members": { + "Boolean TrySerializeValueParameter(Microsoft.Azure.Cosmos.CosmosSerializer, System.IO.Stream ByRef)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Boolean TrySerializeValueParameter(Microsoft.Azure.Cosmos.CosmosSerializer, System.IO.Stream ByRef);IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Microsoft.Azure.Cosmos.PatchOperation Add[T](System.String, T)": { "Type": "Method", "Attributes": [], @@ -1447,24 +1410,6 @@ }, "NestedTypes": {} }, - "Microsoft.Azure.Cosmos.PatchOperation`1;Microsoft.Azure.Cosmos.PatchOperation;IsAbstract:True;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:True;IsSerializable:False": { - "Subclasses": {}, - "Members": { - "T get_Value()": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "T get_Value();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "T Value[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"value\")]": { - "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "T Value;CanRead:True;CanWrite:False;T get_Value();IsAbstract:True;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - } - }, - "NestedTypes": {} - }, "Microsoft.Azure.Cosmos.PatchOperationType;System.Enum;IsAbstract:False;IsSealed:True;IsInterface:False;IsEnum:True;IsClass:False;IsValueType:True;IsNested:False;IsGenericType:False;IsSerializable:True": { "Subclasses": {}, "Members": { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json index 6ad230afe6..793e64e107 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetSDKAPI.json @@ -1469,6 +1469,13 @@ "Attributes": [], "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.AccountProperties] ReadAccountAsync();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.CosmosClient] CreateAndInitializeAsync(System.String, Azure.Core.TokenCredential, System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.String]], Microsoft.Azure.Cosmos.CosmosClientOptions, System.Threading.CancellationToken)[System.Runtime.CompilerServices.AsyncStateMachineAttribute(typeof(Microsoft.Azure.Cosmos.CosmosClient+))]": { + "Type": "Method", + "Attributes": [ + "AsyncStateMachineAttribute" + ], + "MethodInfo": "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.CosmosClient] CreateAndInitializeAsync(System.String, Azure.Core.TokenCredential, System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.String]], Microsoft.Azure.Cosmos.CosmosClientOptions, System.Threading.CancellationToken);IsAbstract:False;IsStatic:True;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Threading.Tasks.Task`1[Microsoft.Azure.Cosmos.CosmosClient] CreateAndInitializeAsync(System.String, System.Collections.Generic.IReadOnlyList`1[System.ValueTuple`2[System.String,System.String]], Microsoft.Azure.Cosmos.CosmosClientOptions, System.Threading.CancellationToken)[System.Runtime.CompilerServices.AsyncStateMachineAttribute(typeof(Microsoft.Azure.Cosmos.CosmosClient+))]": { "Type": "Method", "Attributes": [ @@ -1520,6 +1527,11 @@ ], "MethodInfo": "System.Uri get_Endpoint();IsAbstract:False;IsStatic:False;IsVirtual:True;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Void .ctor(System.String, Azure.Core.TokenCredential, Microsoft.Azure.Cosmos.CosmosClientOptions)": { + "Type": "Constructor", + "Attributes": [], + "MethodInfo": "[Void .ctor(System.String, Azure.Core.TokenCredential, Microsoft.Azure.Cosmos.CosmosClientOptions), Void .ctor(System.String, Azure.Core.TokenCredential, Microsoft.Azure.Cosmos.CosmosClientOptions)]" + }, "Void .ctor(System.String, Microsoft.Azure.Cosmos.CosmosClientOptions)": { "Type": "Constructor", "Attributes": [], @@ -1752,6 +1764,13 @@ "Attributes": [], "MethodInfo": "System.Nullable`1[System.TimeSpan] get_OpenTcpConnectionTimeout();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Nullable`1[System.TimeSpan] get_TokenCredentialBackgroundRefreshInterval()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.Nullable`1[System.TimeSpan] get_TokenCredentialBackgroundRefreshInterval();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.Nullable`1[System.TimeSpan] IdleTcpConnectionTimeout": { "Type": "Property", "Attributes": [], @@ -1767,6 +1786,11 @@ "Attributes": [], "MethodInfo": "System.Nullable`1[System.TimeSpan] OpenTcpConnectionTimeout;CanRead:True;CanWrite:True;System.Nullable`1[System.TimeSpan] get_OpenTcpConnectionTimeout();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_OpenTcpConnectionTimeout(System.Nullable`1[System.TimeSpan]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "System.Nullable`1[System.TimeSpan] TokenCredentialBackgroundRefreshInterval": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "System.Nullable`1[System.TimeSpan] TokenCredentialBackgroundRefreshInterval;CanRead:True;CanWrite:True;System.Nullable`1[System.TimeSpan] get_TokenCredentialBackgroundRefreshInterval();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "System.String ApplicationName": { "Type": "Property", "Attributes": [], @@ -1935,6 +1959,13 @@ "Attributes": [], "MethodInfo": "Void set_SerializerOptions(Microsoft.Azure.Cosmos.CosmosSerializationOptions);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, + "Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan])[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_TokenCredentialBackgroundRefreshInterval(System.Nullable`1[System.TimeSpan]);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, "Void set_WebProxy(System.Net.IWebProxy)": { "Type": "Method", "Attributes": [], diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs index e5d4ae6a64..fbf75f9861 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosAuthorizationTests.cs @@ -82,12 +82,11 @@ public async Task TokenAuthAsync() { LocalEmulatorTokenCredential simpleEmulatorTokenCredential = new LocalEmulatorTokenCredential( "VGhpcyBpcyBhIHNhbXBsZSBzdHJpbmc=", - defaultDateTime: new DateTime(2020, 9, 21, 9, 9, 9, DateTimeKind.Utc)); + defaultDateTime: new DateTime(2030, 9, 21, 9, 9, 9, DateTimeKind.Utc)); using AuthorizationTokenProvider cosmosAuthorization = new AuthorizationTokenProviderTokenCredential( simpleEmulatorTokenCredential, new Uri("https://127.0.0.1:8081"), - requestTimeout: TimeSpan.FromSeconds(30), backgroundTokenCredentialRefreshInterval: TimeSpan.FromSeconds(1)); { @@ -100,7 +99,7 @@ public async Task TokenAuthAsync() AuthorizationTokenType.PrimaryMasterKey); Assert.AreEqual( - "type%3daad%26ver%3d1.0%26sig%3dew0KICAgICAgICAgICAgICAgICJhbGciOiJSUzI1NiIsDQogICAgICAgICAgICAgICAgImtpZCI6InhfOUtTdXNLVTVZY0hmNCIsDQogICAgICAgICAgICAgICAgInR5cCI6IkpXVCINCiAgICAgICAgICAgIH0.ew0KICAgICAgICAgICAgICAgICJvaWQiOiI5NjMxMzAzNC00NzM5LTQzY2ItOTNjZC03NDE5M2FkYmU1YjYiLA0KICAgICAgICAgICAgICAgICJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLA0KICAgICAgICAgICAgICAgICJncm91cHMiOlsNCiAgICAgICAgICAgICAgICAgICAgIjdjZTFkMDAzLTRjYjMtNDg3OS1iN2M1LTc0MDYyYTM1YzY2ZSIsDQogICAgICAgICAgICAgICAgICAgICJlOTlmZjMwYy1jMjI5LTRjNjctYWIyOS0zMGE2YWViYzNlNTgiLA0KICAgICAgICAgICAgICAgICAgICAiNTU0OWJiNjItYzc3Yi00MzA1LWJkYTktOWVjNjZiODVkOWU0IiwNCiAgICAgICAgICAgICAgICAgICAgImM0NGZkNjg1LTVjNTgtNDUyYy1hYWY3LTEzY2U3NTE4NGY2NSIsDQogICAgICAgICAgICAgICAgICAgICJiZTg5NTIxNS1lYWI1LTQzYjctOTUzNi05ZWY4ZmUxMzAzMzAiDQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAibmJmIjoxNjAwNjc5MzQ5LA0KICAgICAgICAgICAgICAgICJleHAiOjE2MDA2ODI5NDksDQogICAgICAgICAgICAgICAgImlhdCI6MTU5NjU5MjMzNSwNCiAgICAgICAgICAgICAgICAiaXNzIjoiaHR0cHM6Ly9zdHMuZmFrZS1pc3N1ZXIubmV0LzdiMTk5OWExLWRmZDctNDQwZS04MjA0LTAwMTcwOTc5Yjk4NCIsDQogICAgICAgICAgICAgICAgImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0LmxvY2FsaG9zdCINCiAgICAgICAgICAgIH0.VkdocGN5QnBjeUJoSUhOaGJYQnNaU0J6ZEhKcGJtYz0" + "type%3daad%26ver%3d1.0%26sig%3dew0KICAgICAgICAgICAgICAgICJhbGciOiJSUzI1NiIsDQogICAgICAgICAgICAgICAgImtpZCI6InhfOUtTdXNLVTVZY0hmNCIsDQogICAgICAgICAgICAgICAgInR5cCI6IkpXVCINCiAgICAgICAgICAgIH0.ew0KICAgICAgICAgICAgICAgICJvaWQiOiI5NjMxMzAzNC00NzM5LTQzY2ItOTNjZC03NDE5M2FkYmU1YjYiLA0KICAgICAgICAgICAgICAgICJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLA0KICAgICAgICAgICAgICAgICJncm91cHMiOlsNCiAgICAgICAgICAgICAgICAgICAgIjdjZTFkMDAzLTRjYjMtNDg3OS1iN2M1LTc0MDYyYTM1YzY2ZSIsDQogICAgICAgICAgICAgICAgICAgICJlOTlmZjMwYy1jMjI5LTRjNjctYWIyOS0zMGE2YWViYzNlNTgiLA0KICAgICAgICAgICAgICAgICAgICAiNTU0OWJiNjItYzc3Yi00MzA1LWJkYTktOWVjNjZiODVkOWU0IiwNCiAgICAgICAgICAgICAgICAgICAgImM0NGZkNjg1LTVjNTgtNDUyYy1hYWY3LTEzY2U3NTE4NGY2NSIsDQogICAgICAgICAgICAgICAgICAgICJiZTg5NTIxNS1lYWI1LTQzYjctOTUzNi05ZWY4ZmUxMzAzMzAiDQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAibmJmIjoxOTE2MjEyMTQ5LA0KICAgICAgICAgICAgICAgICJleHAiOjE5MTYyMTU3NDksDQogICAgICAgICAgICAgICAgImlhdCI6MTU5NjU5MjMzNSwNCiAgICAgICAgICAgICAgICAiaXNzIjoiaHR0cHM6Ly9zdHMuZmFrZS1pc3N1ZXIubmV0LzdiMTk5OWExLWRmZDctNDQwZS04MjA0LTAwMTcwOTc5Yjk4NCIsDQogICAgICAgICAgICAgICAgImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0LmxvY2FsaG9zdCINCiAgICAgICAgICAgIH0.VkdocGN5QnBjeUJoSUhOaGJYQnNaU0J6ZEhKcGJtYz0" , token); Assert.IsNull(payload); } @@ -115,7 +114,7 @@ public async Task TokenAuthAsync() AuthorizationTokenType.PrimaryMasterKey); Assert.AreEqual( - "type%3daad%26ver%3d1.0%26sig%3dew0KICAgICAgICAgICAgICAgICJhbGciOiJSUzI1NiIsDQogICAgICAgICAgICAgICAgImtpZCI6InhfOUtTdXNLVTVZY0hmNCIsDQogICAgICAgICAgICAgICAgInR5cCI6IkpXVCINCiAgICAgICAgICAgIH0.ew0KICAgICAgICAgICAgICAgICJvaWQiOiI5NjMxMzAzNC00NzM5LTQzY2ItOTNjZC03NDE5M2FkYmU1YjYiLA0KICAgICAgICAgICAgICAgICJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLA0KICAgICAgICAgICAgICAgICJncm91cHMiOlsNCiAgICAgICAgICAgICAgICAgICAgIjdjZTFkMDAzLTRjYjMtNDg3OS1iN2M1LTc0MDYyYTM1YzY2ZSIsDQogICAgICAgICAgICAgICAgICAgICJlOTlmZjMwYy1jMjI5LTRjNjctYWIyOS0zMGE2YWViYzNlNTgiLA0KICAgICAgICAgICAgICAgICAgICAiNTU0OWJiNjItYzc3Yi00MzA1LWJkYTktOWVjNjZiODVkOWU0IiwNCiAgICAgICAgICAgICAgICAgICAgImM0NGZkNjg1LTVjNTgtNDUyYy1hYWY3LTEzY2U3NTE4NGY2NSIsDQogICAgICAgICAgICAgICAgICAgICJiZTg5NTIxNS1lYWI1LTQzYjctOTUzNi05ZWY4ZmUxMzAzMzAiDQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAibmJmIjoxNjAwNjc5MzQ5LA0KICAgICAgICAgICAgICAgICJleHAiOjE2MDA2ODI5NDksDQogICAgICAgICAgICAgICAgImlhdCI6MTU5NjU5MjMzNSwNCiAgICAgICAgICAgICAgICAiaXNzIjoiaHR0cHM6Ly9zdHMuZmFrZS1pc3N1ZXIubmV0LzdiMTk5OWExLWRmZDctNDQwZS04MjA0LTAwMTcwOTc5Yjk4NCIsDQogICAgICAgICAgICAgICAgImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0LmxvY2FsaG9zdCINCiAgICAgICAgICAgIH0.VkdocGN5QnBjeUJoSUhOaGJYQnNaU0J6ZEhKcGJtYz0" + "type%3daad%26ver%3d1.0%26sig%3dew0KICAgICAgICAgICAgICAgICJhbGciOiJSUzI1NiIsDQogICAgICAgICAgICAgICAgImtpZCI6InhfOUtTdXNLVTVZY0hmNCIsDQogICAgICAgICAgICAgICAgInR5cCI6IkpXVCINCiAgICAgICAgICAgIH0.ew0KICAgICAgICAgICAgICAgICJvaWQiOiI5NjMxMzAzNC00NzM5LTQzY2ItOTNjZC03NDE5M2FkYmU1YjYiLA0KICAgICAgICAgICAgICAgICJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLA0KICAgICAgICAgICAgICAgICJncm91cHMiOlsNCiAgICAgICAgICAgICAgICAgICAgIjdjZTFkMDAzLTRjYjMtNDg3OS1iN2M1LTc0MDYyYTM1YzY2ZSIsDQogICAgICAgICAgICAgICAgICAgICJlOTlmZjMwYy1jMjI5LTRjNjctYWIyOS0zMGE2YWViYzNlNTgiLA0KICAgICAgICAgICAgICAgICAgICAiNTU0OWJiNjItYzc3Yi00MzA1LWJkYTktOWVjNjZiODVkOWU0IiwNCiAgICAgICAgICAgICAgICAgICAgImM0NGZkNjg1LTVjNTgtNDUyYy1hYWY3LTEzY2U3NTE4NGY2NSIsDQogICAgICAgICAgICAgICAgICAgICJiZTg5NTIxNS1lYWI1LTQzYjctOTUzNi05ZWY4ZmUxMzAzMzAiDQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAibmJmIjoxOTE2MjEyMTQ5LA0KICAgICAgICAgICAgICAgICJleHAiOjE5MTYyMTU3NDksDQogICAgICAgICAgICAgICAgImlhdCI6MTU5NjU5MjMzNSwNCiAgICAgICAgICAgICAgICAiaXNzIjoiaHR0cHM6Ly9zdHMuZmFrZS1pc3N1ZXIubmV0LzdiMTk5OWExLWRmZDctNDQwZS04MjA0LTAwMTcwOTc5Yjk4NCIsDQogICAgICAgICAgICAgICAgImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0LmxvY2FsaG9zdCINCiAgICAgICAgICAgIH0.VkdocGN5QnBjeUJoSUhOaGJYQnNaU0J6ZEhKcGJtYz0" , token); Assert.IsNull(payload); } @@ -130,7 +129,7 @@ public async Task TokenAuthAsync() AuthorizationTokenType.PrimaryMasterKey); Assert.AreEqual( - "type%3daad%26ver%3d1.0%26sig%3dew0KICAgICAgICAgICAgICAgICJhbGciOiJSUzI1NiIsDQogICAgICAgICAgICAgICAgImtpZCI6InhfOUtTdXNLVTVZY0hmNCIsDQogICAgICAgICAgICAgICAgInR5cCI6IkpXVCINCiAgICAgICAgICAgIH0.ew0KICAgICAgICAgICAgICAgICJvaWQiOiI5NjMxMzAzNC00NzM5LTQzY2ItOTNjZC03NDE5M2FkYmU1YjYiLA0KICAgICAgICAgICAgICAgICJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLA0KICAgICAgICAgICAgICAgICJncm91cHMiOlsNCiAgICAgICAgICAgICAgICAgICAgIjdjZTFkMDAzLTRjYjMtNDg3OS1iN2M1LTc0MDYyYTM1YzY2ZSIsDQogICAgICAgICAgICAgICAgICAgICJlOTlmZjMwYy1jMjI5LTRjNjctYWIyOS0zMGE2YWViYzNlNTgiLA0KICAgICAgICAgICAgICAgICAgICAiNTU0OWJiNjItYzc3Yi00MzA1LWJkYTktOWVjNjZiODVkOWU0IiwNCiAgICAgICAgICAgICAgICAgICAgImM0NGZkNjg1LTVjNTgtNDUyYy1hYWY3LTEzY2U3NTE4NGY2NSIsDQogICAgICAgICAgICAgICAgICAgICJiZTg5NTIxNS1lYWI1LTQzYjctOTUzNi05ZWY4ZmUxMzAzMzAiDQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAibmJmIjoxNjAwNjc5MzQ5LA0KICAgICAgICAgICAgICAgICJleHAiOjE2MDA2ODI5NDksDQogICAgICAgICAgICAgICAgImlhdCI6MTU5NjU5MjMzNSwNCiAgICAgICAgICAgICAgICAiaXNzIjoiaHR0cHM6Ly9zdHMuZmFrZS1pc3N1ZXIubmV0LzdiMTk5OWExLWRmZDctNDQwZS04MjA0LTAwMTcwOTc5Yjk4NCIsDQogICAgICAgICAgICAgICAgImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0LmxvY2FsaG9zdCINCiAgICAgICAgICAgIH0.VkdocGN5QnBjeUJoSUhOaGJYQnNaU0J6ZEhKcGJtYz0" + "type%3daad%26ver%3d1.0%26sig%3dew0KICAgICAgICAgICAgICAgICJhbGciOiJSUzI1NiIsDQogICAgICAgICAgICAgICAgImtpZCI6InhfOUtTdXNLVTVZY0hmNCIsDQogICAgICAgICAgICAgICAgInR5cCI6IkpXVCINCiAgICAgICAgICAgIH0.ew0KICAgICAgICAgICAgICAgICJvaWQiOiI5NjMxMzAzNC00NzM5LTQzY2ItOTNjZC03NDE5M2FkYmU1YjYiLA0KICAgICAgICAgICAgICAgICJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLA0KICAgICAgICAgICAgICAgICJncm91cHMiOlsNCiAgICAgICAgICAgICAgICAgICAgIjdjZTFkMDAzLTRjYjMtNDg3OS1iN2M1LTc0MDYyYTM1YzY2ZSIsDQogICAgICAgICAgICAgICAgICAgICJlOTlmZjMwYy1jMjI5LTRjNjctYWIyOS0zMGE2YWViYzNlNTgiLA0KICAgICAgICAgICAgICAgICAgICAiNTU0OWJiNjItYzc3Yi00MzA1LWJkYTktOWVjNjZiODVkOWU0IiwNCiAgICAgICAgICAgICAgICAgICAgImM0NGZkNjg1LTVjNTgtNDUyYy1hYWY3LTEzY2U3NTE4NGY2NSIsDQogICAgICAgICAgICAgICAgICAgICJiZTg5NTIxNS1lYWI1LTQzYjctOTUzNi05ZWY4ZmUxMzAzMzAiDQogICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAibmJmIjoxOTE2MjEyMTQ5LA0KICAgICAgICAgICAgICAgICJleHAiOjE5MTYyMTU3NDksDQogICAgICAgICAgICAgICAgImlhdCI6MTU5NjU5MjMzNSwNCiAgICAgICAgICAgICAgICAiaXNzIjoiaHR0cHM6Ly9zdHMuZmFrZS1pc3N1ZXIubmV0LzdiMTk5OWExLWRmZDctNDQwZS04MjA0LTAwMTcwOTc5Yjk4NCIsDQogICAgICAgICAgICAgICAgImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0LmxvY2FsaG9zdCINCiAgICAgICAgICAgIH0.VkdocGN5QnBjeUJoSUhOaGJYQnNaU0J6ZEhKcGJtYz0" , token); Assert.IsNull(payload); } @@ -145,7 +144,6 @@ public void TestTokenCredentialCacheMaxAndMinValues() new TokenCredentialCache( new Mock().Object, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: toLarge); Assert.Fail("Should throw ArgumentException"); } @@ -159,7 +157,6 @@ public void TestTokenCredentialCacheMaxAndMinValues() new TokenCredentialCache( new Mock().Object, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: TimeSpan.MinValue); Assert.Fail("Should throw ArgumentException"); } @@ -173,7 +170,6 @@ public void TestTokenCredentialCacheMaxAndMinValues() new TokenCredentialCache( new Mock().Object, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: TimeSpan.Zero); Assert.Fail("Should throw ArgumentException"); } @@ -187,7 +183,6 @@ public void TestTokenCredentialCacheMaxAndMinValues() new TokenCredentialCache( new Mock().Object, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: TimeSpan.FromMilliseconds(-1)); Assert.Fail("Should throw ArgumentException"); } @@ -196,45 +191,15 @@ public void TestTokenCredentialCacheMaxAndMinValues() Assert.IsTrue(ae.ToString().Contains("backgroundTokenCredentialRefreshInterval")); } - try - { - new TokenCredentialCache( - new Mock().Object, - CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.MinValue, - backgroundTokenCredentialRefreshInterval: TimeSpan.FromMinutes(1)); - Assert.Fail("Should throw ArgumentException"); - } - catch (ArgumentException ae) - { - Assert.IsTrue(ae.ToString().Contains("backgroundTokenCredentialRefreshInterval")); - } - - try - { - new TokenCredentialCache( - new Mock().Object, - CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.Zero, - backgroundTokenCredentialRefreshInterval: TimeSpan.FromMinutes(1)); - Assert.Fail("Should throw ArgumentException"); - } - catch (ArgumentException ae) - { - Assert.IsTrue(ae.ToString().Contains("backgroundTokenCredentialRefreshInterval")); - } - // Which is roughly 24 days using TokenCredentialCache token = new TokenCredentialCache( new Mock().Object, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: TimeSpan.FromMilliseconds(Int32.MaxValue)); using TokenCredentialCache disableBackgroundTask = new TokenCredentialCache( new Mock().Object, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: TimeSpan.MaxValue); } @@ -249,41 +214,12 @@ public async Task TestTokenCredentialCacheHappyPathAsync() } } - [TestMethod] - public async Task TestTokenCredentialTimeoutAsync() - { - TestTokenCredential testTokenCredential = new TestTokenCredential(async () => - { - await Task.Delay(-1); - - return new AccessToken("AccessToken", DateTimeOffset.MaxValue); - }); - - TimeSpan timeout = TimeSpan.FromSeconds(1); - using (TokenCredentialCache tokenCredentialCache = this.CreateTokenCredentialCache( - tokenCredential: testTokenCredential, - requestTimeout: timeout)) - { - try - { - await tokenCredentialCache.GetTokenAsync(NoOpTrace.Singleton); - Assert.Fail("TokenCredentialCache.GetTokenAsync() is expected to fail but succeeded"); - } - catch (CosmosException cosmosException) - { - Assert.AreEqual(HttpStatusCode.RequestTimeout, cosmosException.StatusCode); - Assert.AreEqual((int)Azure.Documents.SubStatusCodes.FailedToGetAadToken, cosmosException.SubStatusCode); - Assert.AreEqual($"TokenCredential.GetTokenAsync request timed out after {timeout}", cosmosException.InnerException.Message); - } - } - } - [TestMethod] public async Task TestTokenCredentialErrorAsync() { - Exception exception = new Exception(); + Exception exceptionToBeThrown = new Exception("Test Error Message"); - TestTokenCredential testTokenCredential = new TestTokenCredential(() => throw exception); + TestTokenCredential testTokenCredential = new TestTokenCredential(() => throw exceptionToBeThrown); using (TokenCredentialCache tokenCredentialCache = this.CreateTokenCredentialCache(testTokenCredential)) { @@ -292,18 +228,17 @@ public async Task TestTokenCredentialErrorAsync() await tokenCredentialCache.GetTokenAsync(NoOpTrace.Singleton); Assert.Fail("TokenCredentialCache.GetTokenAsync() is expected to fail but succeeded"); } - catch (CosmosException cosmosException) + catch (Exception exception) { - Assert.AreEqual(HttpStatusCode.Unauthorized, cosmosException.StatusCode); - Assert.AreEqual((int)Azure.Documents.SubStatusCodes.FailedToGetAadToken, cosmosException.SubStatusCode); - + // It should just throw the original exception and not be wrapped in a CosmosException. + // This avoids any confusion on where the error was thrown from. Assert.IsTrue(object.ReferenceEquals( exception, - cosmosException.InnerException)); + exceptionToBeThrown)); } // TokenCredential.GetTokenAsync() is retried for 3 times, so it should have been invoked for 4 times. - Assert.AreEqual(3, testTokenCredential.NumTimesInvoked); + Assert.AreEqual(2, testTokenCredential.NumTimesInvoked); } } @@ -399,14 +334,13 @@ public async Task TestTokenCredentialFailedToRefreshAsync() await tokenCredentialCache.GetTokenAsync(trace); Assert.Fail("TokenCredentialCache.GetTokenAsync() is expected to fail but succeeded"); } - catch (CosmosException cosmosException) + catch (Exception thrownException) { - Assert.AreEqual(HttpStatusCode.Unauthorized, cosmosException.StatusCode); - Assert.AreEqual((int)Azure.Documents.SubStatusCodes.FailedToGetAadToken, cosmosException.SubStatusCode); - + // It should just throw the original exception and not be wrapped in a CosmosException + // This avoids any confusion on where the error was thrown from. Assert.IsTrue(object.ReferenceEquals( exception, - cosmosException.InnerException)); + thrownException)); } } } @@ -441,13 +375,11 @@ public async Task TestTokenCredentialMultiThreadAsync() } private TokenCredentialCache CreateTokenCredentialCache( - TokenCredential tokenCredential, - TimeSpan? requestTimeout = null) + TokenCredential tokenCredential) { return new TokenCredentialCache( tokenCredential, CosmosAuthorizationTests.AccountEndpoint, - requestTimeout: requestTimeout ?? TimeSpan.FromSeconds(15), backgroundTokenCredentialRefreshInterval: TimeSpan.FromSeconds(5)); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientTests.cs index 78b44af960..4d531cbcb7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Tests { using System; using System.Collections.Generic; + using System.Globalization; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -28,6 +29,11 @@ public async Task TestDispose() TransactionalBatch batch = container.CreateTransactionalBatch(new PartitionKey("asdf")); batch.ReadItem("Test"); + FeedIterator feedIterator1 = container.GetItemQueryIterator(); + FeedIterator feedIterator2 = container.GetItemQueryIterator(queryText: "select * from T"); + FeedIterator feedIterator3 = database.GetContainerQueryIterator(queryText: "select * from T"); + + string userAgent = cosmosClient.ClientContext.UserAgent; // Dispose should be idempotent cosmosClient.Dispose(); cosmosClient.Dispose(); @@ -42,18 +48,32 @@ public async Task TestDispose() () => container.Scripts.ReadTriggerAsync("asdf"), () => container.Scripts.ReadUserDefinedFunctionAsync("asdf"), () => batch.ExecuteAsync(), - () => container.GetItemQueryIterator(queryText: "select * from T").ReadNextAsync(), - () => container.GetItemQueryIterator().ReadNextAsync(), + () => feedIterator1.ReadNextAsync(), + () => feedIterator2.ReadNextAsync(), + () => feedIterator3.ReadNextAsync(), }; foreach (Func asyncFunc in validateAsync) { try { - await asyncFunc(); + await asyncFunc(); Assert.Fail("Should throw ObjectDisposedException"); } - catch (ObjectDisposedException) { } + catch (CosmosObjectDisposedException e) + { + string expectedMessage = $"Cannot access a disposed 'CosmosClient'. Follow best practices and use the CosmosClient as a singleton." + + $" CosmosClient was disposed at: {cosmosClient.DisposedDateTimeUtc.Value.ToString("o", CultureInfo.InvariantCulture)}; CosmosClient Endpoint: https://localtestcosmos.documents.azure.com/; Created at: {cosmosClient.ClientConfigurationTraceDatum.ClientCreatedDateTimeUtc.ToString("o", CultureInfo.InvariantCulture)}; UserAgent: {userAgent};"; + Assert.IsTrue(e.Message.Contains(expectedMessage)); + string diagnostics = e.Diagnostics.ToString(); + Assert.IsNotNull(diagnostics); + Assert.IsFalse(diagnostics.Contains("NoOp")); + Assert.IsTrue(diagnostics.Contains("Client Configuration")); + string exceptionString = e.ToString(); + Assert.IsTrue(exceptionString.Contains(diagnostics)); + Assert.IsTrue(exceptionString.Contains(e.Message)); + Assert.IsTrue(exceptionString.Contains(e.StackTrace)); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs index 2a93213e0f..1ea21d2123 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosContainerSettingsTests.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Collections.ObjectModel; using System.IO; using System.Linq; + using System.Text; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; @@ -43,6 +44,14 @@ public void ValidateSerialization() Assert.AreEqual(containerSettings.PartitionKeyPath, response.PartitionKeyPath); } + [TestMethod] + public void ValidateClientEncryptionPolicyDeserialization() + { + ClientEncryptionPolicy policy = MockCosmosUtil.Serializer.FromStream(new MemoryStream( + Encoding.UTF8.GetBytes("{ 'policyFormatVersion': 2, 'newproperty': 'value' }"))); + Assert.AreEqual(2, policy.PolicyFormatVersion); + } + [TestMethod] public void DefaultIncludesShouldNotBePopulated() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs index b67f8484dd..b1aedb0fce 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosItemUnitTests.cs @@ -201,7 +201,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_CreateStream() partitionKey: partitionKey, streamPayload: itemStream)) { - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } } @@ -225,7 +225,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_UpsertStream() partitionKey: partitionKey, streamPayload: itemStream)) { - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } } @@ -250,7 +250,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_ReplaceStream() id: testItem.id, streamPayload: itemStream)) { - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } } @@ -274,7 +274,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_ReadStream() partitionKey: partitionKey, id: testItem.id)) { - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } } @@ -296,7 +296,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_DeleteStream() partitionKey: partitionKey, id: testItem.id)) { - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } @@ -323,7 +323,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_PatchStream() id: testItem.id, patchOperations: patch)) { - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } @@ -342,7 +342,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_Create() ItemResponse response = await container.CreateItemAsync( testItem, partitionKey: partitionKey); - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -360,7 +360,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_Upsert() ItemResponse response = await container.UpsertItemAsync( testItem, partitionKey: partitionKey); - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -378,7 +378,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_Replace() ItemResponse response = await container.ReplaceItemAsync( testItem, testItem.id); - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -396,7 +396,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_Read() ItemResponse response = await container.ReadItemAsync( id: testItem.id, partitionKey: partitionKey); - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -415,7 +415,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_Delete() partitionKey: partitionKey, id: testItem.id); - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -440,7 +440,7 @@ public async Task AllowBatchingRequestsSendsToExecutor_Patch() partitionKey, patch); - mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + mockedExecutor.Verify(c => c.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] @@ -928,7 +928,7 @@ private Mock GetMockedBatchExcecutor() Mock mockedExecutor = new Mock(); mockedExecutor - .Setup(e => e.AddAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(e => e.AddAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new TransactionalBatchOperationResult(HttpStatusCode.OK) { }); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosNullReferenceExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosNullReferenceExceptionTests.cs new file mode 100644 index 0000000000..a97b56189b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosNullReferenceExceptionTests.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ +namespace Microsoft.Azure.Cosmos.Tests +{ + using System; + using System.Threading; + using Microsoft.Azure.Cosmos.Tracing; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Tests for scenarios. + /// + [TestClass] + public class CosmosNullReferenceExceptionTests + { + [TestMethod] + public void CosmosNullRefWrapingTest() + { + string message = "Test1234 NullReferenceException"; + NullReferenceException nullReferenceException; + try + { + throw new NullReferenceException(message); + } + catch(NullReferenceException nre) + { + nullReferenceException = nre; + } + + string rootTraceName = "TestRoot"; + ITrace trace = Trace.GetRootTrace(rootTraceName); + using (trace.StartChild("startChild")) { } + + CosmosNullReferenceException cosmosNullReferenceException = new CosmosNullReferenceException( + nullReferenceException, + trace); + + Assert.AreEqual(nullReferenceException.StackTrace, cosmosNullReferenceException.StackTrace); + Assert.AreEqual(nullReferenceException.InnerException, cosmosNullReferenceException.InnerException); + Assert.AreEqual(nullReferenceException.Data, cosmosNullReferenceException.Data); + + Assert.IsTrue(cosmosNullReferenceException.Message.Contains(message)); + Assert.IsTrue(cosmosNullReferenceException.Message.Contains(rootTraceName)); + Assert.AreNotEqual(nullReferenceException.Message, cosmosNullReferenceException.Message); + string cosmosToString = cosmosNullReferenceException.ToString(); + Assert.IsFalse(cosmosToString.Contains("Microsoft.Azure.Cosmos.CosmosNullReferenceException"), $"The internal wrapper exception should not be exposed to users. {cosmosToString}"); + Assert.IsTrue(cosmosToString.Contains(message)); + Assert.IsTrue(cosmosToString.Contains(rootTraceName)); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosSerializerCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosSerializerCoreTests.cs index e5caf0e635..1144d4371f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosSerializerCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosSerializerCoreTests.cs @@ -118,6 +118,13 @@ public void ValidatePatchOperationSerialization() // custom serializer is used since there is Add operation type also using (Stream stream = serializerCore.ToStream(new PatchSpec(patch, patchRequestOptions))) { } Assert.AreEqual(1, toCount); + + patch.Clear(); + toCount = 0; + patch.Add(PatchOperation.Add("/addPath", new CosmosJsonDotNetSerializer().ToStream("addValue"))); + // custom serializer is not used since the input value is of type stream + using (Stream stream = serializerCore.ToStream(new PatchSpec(patch, patchRequestOptions))) { } + Assert.AreEqual(0, toCount); } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ExceptionlessTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ExceptionlessTests.cs index 1e16574152..dc53dfa741 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ExceptionlessTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ExceptionlessTests.cs @@ -297,7 +297,7 @@ async Task sendFunc(HttpRequestMessage httpRequest) return await Task.FromResult(new HttpResponseMessage((HttpStatusCode)responseStatusCode)); } - GatewayStoreModel storeModel = MockGatewayStoreModel(sendFunc); + using GatewayStoreModel storeModel = MockGatewayStoreModel(sendFunc); using (new ActivityScope(Guid.NewGuid())) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs index d9937f49ad..e45f02086f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/FeedRange/ChangeFeedIteratorCoreTests.cs @@ -12,9 +12,9 @@ namespace Microsoft.Azure.Cosmos.Tests.FeedRange using System.Threading.Tasks; using Microsoft.Azure.Cosmos.ChangeFeed; using Microsoft.Azure.Cosmos.CosmosElements; - using Microsoft.Azure.Cosmos.Json.Interop; using Microsoft.Azure.Cosmos.Pagination; using Microsoft.Azure.Cosmos.Query.Core.Monads; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Tests.Pagination; using Microsoft.Azure.Cosmos.Tracing; using Microsoft.Azure.Documents; @@ -208,6 +208,199 @@ public async Task ChangeFeedIteratorCore_HandlesSplitsThroughPipeline() Assert.AreEqual(numItems, count, seed); } + [TestMethod] + public async Task ChangeFeedIteratorCore_OnCosmosException_HasMoreResults() + { + CosmosException exception = CosmosExceptionFactory.CreateInternalServerErrorException("something's broken", new Headers()); + IDocumentContainer documentContainer = await CreateDocumentContainerAsync( + numItems:0, + failureConfigs: new FlakyDocumentContainer.FailureConfigs( + inject429s: false, + injectEmptyPages: false, + returnFailure: exception)); + + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + documentContainer, + ChangeFeedMode.Incremental, + new ChangeFeedRequestOptions(), + ChangeFeedStartFrom.Now(), + this.MockClientContext()); + + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.AreEqual(HttpStatusCode.InternalServerError, responseMessage.StatusCode); + Assert.IsFalse(changeFeedIteratorCore.HasMoreResults); + } + + [TestMethod] + public async Task ChangeFeedIteratorCore_OnRetriableCosmosException_HasMoreResults() + { + CosmosException exception = CosmosExceptionFactory.CreateThrottledException("retry", new Headers()); + IDocumentContainer documentContainer = await CreateDocumentContainerAsync( + numItems: 0, + failureConfigs: new FlakyDocumentContainer.FailureConfigs( + inject429s: false, + injectEmptyPages: false, + returnFailure: exception)); + + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + documentContainer, + ChangeFeedMode.Incremental, + new ChangeFeedRequestOptions(), + ChangeFeedStartFrom.Beginning(), + this.MockClientContext()); + + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.AreEqual(HttpStatusCode.TooManyRequests, responseMessage.StatusCode); + Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); + } + + [TestMethod] + public async Task ChangeFeedIteratorCore_OnNonCosmosExceptions_HasMoreResults() + { + Exception exception = new NotImplementedException(); + IDocumentContainer documentContainer = await CreateDocumentContainerAsync( + numItems: 0, + failureConfigs: new FlakyDocumentContainer.FailureConfigs( + inject429s: false, + injectEmptyPages: false, + returnFailure: exception)); + + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + documentContainer, + ChangeFeedMode.Incremental, + new ChangeFeedRequestOptions(), + ChangeFeedStartFrom.Beginning(), + this.MockClientContext()); + + try + { + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.Fail("Should have thrown"); + } + catch (Exception ex) + { + Assert.AreEqual(exception, ex); + Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); + } + } + + [TestMethod] + public async Task ChangeFeedIteratorCore_OnTaskCanceledException_HasMoreResultsAndDiagnostics() + { + Exception exception = new TaskCanceledException(); + IDocumentContainer documentContainer = await CreateDocumentContainerAsync( + numItems: 0, + failureConfigs: new FlakyDocumentContainer.FailureConfigs( + inject429s: false, + injectEmptyPages: false, + throwException: exception)); + + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + documentContainer, + ChangeFeedMode.Incremental, + new ChangeFeedRequestOptions(), + ChangeFeedStartFrom.Beginning(), + this.MockClientContext()); + + try + { + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.Fail("Should have thrown"); + } + catch (OperationCanceledException ex) + { + Assert.IsTrue(ex is CosmosOperationCanceledException); + Assert.IsNotNull(((CosmosOperationCanceledException)ex).Diagnostics); + Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); + } + } + + /// + /// If an unhandled exception occurs within the NetworkAttachedDocumentContainer, the exception is transmitted but it does not break the enumerators + /// + [TestMethod] + public async Task ChangeFeedIteratorCore_OnUnhandledException_HasMoreResults() + { + Exception exception = new Exception("oh no"); + IDocumentContainer documentContainer = await CreateDocumentContainerAsync( + numItems: 0, + failureConfigs: new FlakyDocumentContainer.FailureConfigs( + inject429s: false, + injectEmptyPages: false, + throwException: exception)); + + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + documentContainer, + ChangeFeedMode.Incremental, + new ChangeFeedRequestOptions(), + ChangeFeedStartFrom.Beginning(), + this.MockClientContext()); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + try + { + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.Fail("Should have thrown"); + } + catch (Exception ex) + { + Assert.AreEqual(exception, ex); + Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); + } + + // If read a second time, it should not throw any missing page errors related to enumerators + try + { + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.Fail("Should have thrown"); + } + catch (Exception ex) + { + // TryCatch wraps any exception + Assert.AreEqual(exception, ex); + Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); + } + } + + [TestMethod] + public async Task ChangeFeedIteratorCore_CancellationToken_FlowsThrough() + { + // Generate constant 429 + CosmosException exception = CosmosExceptionFactory.CreateThrottledException("retry", new Headers()); + IDocumentContainer documentContainer = await CreateDocumentContainerAsync( + numItems: 0, + failureConfigs: new FlakyDocumentContainer.FailureConfigs( + inject429s: false, + injectEmptyPages: false, + returnFailure: exception)); + + ChangeFeedIteratorCore changeFeedIteratorCore = new ChangeFeedIteratorCore( + documentContainer, + ChangeFeedMode.Incremental, + new ChangeFeedRequestOptions(), + ChangeFeedStartFrom.Beginning(), + this.MockClientContext()); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + try + { + // First request triggers initialization, we don't cancel it + ResponseMessage responseMessage = await changeFeedIteratorCore.ReadNextAsync(); + Assert.AreEqual(HttpStatusCode.TooManyRequests, responseMessage.StatusCode); + + // Should be initialized, let's see if cancellation flows through + await changeFeedIteratorCore.ReadNextAsync(cancellationTokenSource.Token); + Assert.Fail("Should have thrown"); + } + catch (OperationCanceledException) + { + } + + Assert.IsTrue(changeFeedIteratorCore.HasMoreResults); + } + private static CosmosArray GetChanges(Stream stream) { using (MemoryStream memoryStream = new MemoryStream()) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs index 58af008c21..cfaf0f19c2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayAddressCacheTests.cs @@ -17,6 +17,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Documents; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Tests; + using Microsoft.Azure.Cosmos.Tracing; /// /// Tests for . @@ -25,7 +26,7 @@ namespace Microsoft.Azure.Cosmos public class GatewayAddressCacheTests { private const string DatabaseAccountApiEndpoint = "https://endpoint.azure.com"; - private Mock mockTokenProvider; + private Mock mockTokenProvider; private Mock mockServiceConfigReader; private int targetReplicaSetSize = 4; private PartitionKeyRangeIdentity testPartitionKeyRangeIdentity; @@ -34,9 +35,9 @@ public class GatewayAddressCacheTests public GatewayAddressCacheTests() { - this.mockTokenProvider = new Mock(); - this.mockTokenProvider.Setup(foo => foo.GetUserAuthorizationAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new ValueTask<(string, string)>(("token!", null))); + this.mockTokenProvider = new Mock(); + this.mockTokenProvider.Setup(foo => foo.GetUserAuthorizationTokenAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(new ValueTask("token!")); this.mockServiceConfigReader = new Mock(); this.mockServiceConfigReader.Setup(foo => foo.SystemReplicationPolicy).Returns(new ReplicationPolicy() { MaxReplicaSetSize = this.targetReplicaSetSize }); this.mockServiceConfigReader.Setup(foo => foo.UserReplicationPolicy).Returns(new ReplicationPolicy() { MaxReplicaSetSize = this.targetReplicaSetSize }); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs index 00d1c2d451..82f7a9e10a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GatewayStoreModelTest.cs @@ -50,7 +50,7 @@ public async Task TestOpenAsyncFailFast() bool failedToResolve = false; bool didNotRetry = false; - const string failedToResolveMessage = "Fail to reach gateway endpoint https://veryrandomurl123456789.documents.azure.com/, "; + const string failedToResolveMessage = "GlobalEndpointManager: Fail to reach gateway endpoint https://veryrandomurl123456789.documents.azure.com/, "; string didNotRetryMessage = null; void TraceHandler(string message) @@ -116,11 +116,11 @@ public async Task TestRetries() Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); ISessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -191,14 +191,17 @@ public async Task TestApplySessionForMasterOperation() dsr.Headers.Add(HttpConstants.HttpHeaders.SessionToken, Guid.NewGuid().ToString()); - await GatewayStoreModel.ApplySessionTokenAsync( - dsr, - ConsistencyLevel.Session, - new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), this.GetGatewayStoreModelForConsistencyTest(), null, null).Object); + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + await GatewayStoreModel.ApplySessionTokenAsync( + dsr, + ConsistencyLevel.Session, + new Mock().Object, + partitionKeyRangeCache: new Mock(null, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object); - Assert.IsNull(dsr.Headers[HttpConstants.HttpHeaders.SessionToken]); + Assert.IsNull(dsr.Headers[HttpConstants.HttpHeaders.SessionToken]); + }); } } @@ -214,15 +217,17 @@ await GatewayStoreModel.ApplySessionTokenAsync( dsrQueryPlan.Headers.Add(HttpConstants.HttpHeaders.SessionToken, Guid.NewGuid().ToString()); - await GatewayStoreModel.ApplySessionTokenAsync( - dsrQueryPlan, - ConsistencyLevel.Session, - new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), this.GetGatewayStoreModelForConsistencyTest(), null, null).Object); - - - Assert.IsNull(dsrQueryPlan.Headers[HttpConstants.HttpHeaders.SessionToken]); + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + await GatewayStoreModel.ApplySessionTokenAsync( + dsrQueryPlan, + ConsistencyLevel.Session, + new Mock().Object, + partitionKeyRangeCache: new Mock(null, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object); + + Assert.IsNull(dsrQueryPlan.Headers[HttpConstants.HttpHeaders.SessionToken]); + }); } [TestMethod] @@ -263,15 +268,17 @@ public async Task TestApplySessionForDataOperation() string dsrSessionToken = Guid.NewGuid().ToString(); dsr.Headers.Add(HttpConstants.HttpHeaders.SessionToken, dsrSessionToken); - await GatewayStoreModel.ApplySessionTokenAsync( - dsr, - ConsistencyLevel.Session, - new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), this.GetGatewayStoreModelForConsistencyTest(), null, null).Object); - + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + await GatewayStoreModel.ApplySessionTokenAsync( + dsr, + ConsistencyLevel.Session, + new Mock().Object, + partitionKeyRangeCache: new Mock(null, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object); - Assert.AreEqual(dsrSessionToken, dsr.Headers[HttpConstants.HttpHeaders.SessionToken]); + Assert.AreEqual(dsrSessionToken, dsr.Headers[HttpConstants.HttpHeaders.SessionToken]); + }); } { @@ -286,22 +293,24 @@ await GatewayStoreModel.ApplySessionTokenAsync( Mock sMock = new Mock(); sMock.Setup(x => x.ResolveGlobalSessionToken(dsrNoSessionToken)).Returns(dsrSessionToken); - await GatewayStoreModel.ApplySessionTokenAsync( - dsrNoSessionToken, - ConsistencyLevel.Session, - sMock.Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), this.GetGatewayStoreModelForConsistencyTest(), null, null).Object); - - - if (dsrNoSessionToken.IsReadOnlyRequest || dsrNoSessionToken.OperationType == OperationType.Batch) - { - Assert.AreEqual(dsrSessionToken, dsrNoSessionToken.Headers[HttpConstants.HttpHeaders.SessionToken]); - } - else + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => { - Assert.IsNull(dsrNoSessionToken.Headers[HttpConstants.HttpHeaders.SessionToken]); - } + await GatewayStoreModel.ApplySessionTokenAsync( + dsrNoSessionToken, + ConsistencyLevel.Session, + sMock.Object, + partitionKeyRangeCache: new Mock(null, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object); + + if (dsrNoSessionToken.IsReadOnlyRequest || dsrNoSessionToken.OperationType == OperationType.Batch) + { + Assert.AreEqual(dsrSessionToken, dsrNoSessionToken.Headers[HttpConstants.HttpHeaders.SessionToken]); + } + else + { + Assert.IsNull(dsrNoSessionToken.Headers[HttpConstants.HttpHeaders.SessionToken]); + } + }); } { @@ -369,15 +378,17 @@ await GatewayStoreModel.ApplySessionTokenAsync( string sessionToken = Guid.NewGuid().ToString(); dsrSprocExecute.Headers.Add(HttpConstants.HttpHeaders.SessionToken, sessionToken); - await GatewayStoreModel.ApplySessionTokenAsync( - dsrSprocExecute, - ConsistencyLevel.Session, - new Mock().Object, - partitionKeyRangeCache: new Mock(null, null, null).Object, - clientCollectionCache: new Mock(new SessionContainer("testhost"), this.GetGatewayStoreModelForConsistencyTest(), null, null).Object); - - - Assert.AreEqual(sessionToken, dsrSprocExecute.Headers[HttpConstants.HttpHeaders.SessionToken]); + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + await GatewayStoreModel.ApplySessionTokenAsync( + dsrSprocExecute, + ConsistencyLevel.Session, + new Mock().Object, + partitionKeyRangeCache: new Mock(null, null, null).Object, + clientCollectionCache: new Mock(new SessionContainer("testhost"), gatewayStoreModel, null, null).Object); + + Assert.AreEqual(sessionToken, dsrSprocExecute.Headers[HttpConstants.HttpHeaders.SessionToken]); + }); } [TestMethod] @@ -392,11 +403,11 @@ public async Task TestErrorResponsesProvideBody() Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); ISessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -454,11 +465,11 @@ private async Task GatewayStoreModel_Exception_UpdateSessionTokenOnKnownExceptio Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); SessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -518,11 +529,11 @@ private async Task GatewayStoreModel_Exception_NotUpdateSessionTokenOnKnownExcep Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); SessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -567,9 +578,9 @@ private async Task GatewayStoreModel_Exception_NotUpdateSessionTokenOnKnownExcep [TestMethod] public async Task TestSessionTokenForSessionConsistentResourceType() { - GatewayStoreModel storeModel = GetGatewayStoreModelForConsistencyTest(); - - using (DocumentServiceRequest request = + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + using (DocumentServiceRequest request = DocumentServiceRequest.Create( Documents.OperationType.Read, Documents.ResourceType.Collection, @@ -577,9 +588,10 @@ public async Task TestSessionTokenForSessionConsistentResourceType() new MemoryStream(Encoding.UTF8.GetBytes("collection")), AuthorizationTokenType.PrimaryMasterKey, null)) - { - await TestGatewayStoreModelProcessMessageAsync(storeModel, request); - } + { + await this.TestGatewayStoreModelProcessMessageAsync(gatewayStoreModel, request); + } + }); } /// @@ -589,9 +601,9 @@ public async Task TestSessionTokenForSessionConsistentResourceType() [TestMethod] public async Task TestSessionTokenForSessionInconsistentResourceType() { - GatewayStoreModel storeModel = GetGatewayStoreModelForConsistencyTest(); - - using (DocumentServiceRequest request = + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + using (DocumentServiceRequest request = DocumentServiceRequest.Create( Documents.OperationType.Query, Documents.ResourceType.Document, @@ -599,9 +611,10 @@ public async Task TestSessionTokenForSessionInconsistentResourceType() new MemoryStream(Encoding.UTF8.GetBytes("document")), AuthorizationTokenType.PrimaryMasterKey, null)) - { - await TestGatewayStoreModelProcessMessageAsync(storeModel, request); - } + { + await this.TestGatewayStoreModelProcessMessageAsync(gatewayStoreModel, request); + } + }); } /// @@ -611,9 +624,9 @@ public async Task TestSessionTokenForSessionInconsistentResourceType() [TestMethod] public async Task TestSessionTokenAvailability() { - GatewayStoreModel storeModel = GetGatewayStoreModelForConsistencyTest(); - - using (DocumentServiceRequest request = + await this.GetGatewayStoreModelForConsistencyTest(async (gatewayStoreModel) => + { + using (DocumentServiceRequest request = DocumentServiceRequest.Create( Documents.OperationType.Read, Documents.ResourceType.Collection, @@ -621,22 +634,22 @@ public async Task TestSessionTokenAvailability() new MemoryStream(Encoding.UTF8.GetBytes("collection")), AuthorizationTokenType.PrimaryMasterKey, null)) - { - await TestGatewayStoreModelProcessMessageAsync(storeModel, request); - } - - using (DocumentServiceRequest request = - DocumentServiceRequest.Create( - Documents.OperationType.Query, - Documents.ResourceType.Document, - new Uri("https://foo.com/dbs/db1/colls/coll1", UriKind.Absolute), - new MemoryStream(Encoding.UTF8.GetBytes("document")), - AuthorizationTokenType.PrimaryMasterKey, - null)) - { - await TestGatewayStoreModelProcessMessageAsync(storeModel, request); - } + { + await this.TestGatewayStoreModelProcessMessageAsync(gatewayStoreModel, request); + } + using (DocumentServiceRequest request = + DocumentServiceRequest.Create( + Documents.OperationType.Query, + Documents.ResourceType.Document, + new Uri("https://foo.com/dbs/db1/colls/coll1", UriKind.Absolute), + new MemoryStream(Encoding.UTF8.GetBytes("document")), + AuthorizationTokenType.PrimaryMasterKey, + null)) + { + await this.TestGatewayStoreModelProcessMessageAsync(gatewayStoreModel, request); + } + }); } [TestMethod] @@ -664,11 +677,11 @@ private async Task GatewayStoreModel_Exceptionless_UpdateSessionTokenOnKnownResp Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); SessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -730,11 +743,11 @@ private async Task GatewayStoreModel_Exceptionless_NotUpdateSessionTokenOnKnownR Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); SessionContainer sessionContainer = new SessionContainer(string.Empty); DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler messageHandler = new MockMessageHandler(sendFunc); - GatewayStoreModel storeModel = new GatewayStoreModel( + using GatewayStoreModel storeModel = new GatewayStoreModel( endpointManager, sessionContainer, ConsistencyLevel.Eventual, @@ -779,9 +792,10 @@ protected override async Task SendAsync(HttpRequestMessage } } - private GatewayStoreModel GetGatewayStoreModelForConsistencyTest() + private async Task GetGatewayStoreModelForConsistencyTest( + Func executeWithGatewayStoreModel) { - Func> messageHandler = async request => + static async Task messageHandler(HttpRequestMessage request) { String content = await request.Content.ReadAsStringAsync(); if (content.Equals("document")) @@ -797,17 +811,16 @@ private GatewayStoreModel GetGatewayStoreModelForConsistencyTest() } else { - IEnumerable enumerable; - Assert.IsFalse(request.Headers.TryGetValues("x-ms-session-token", out enumerable)); + Assert.IsFalse(request.Headers.TryGetValues("x-ms-session-token", out IEnumerable enumerable)); } return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Response") }; - }; + } Mock mockDocumentClient = new Mock(); mockDocumentClient.Setup(client => client.ServiceEndpoint).Returns(new Uri("https://foo")); mockDocumentClient.Setup(client => client.ConsistencyLevel).Returns(Documents.ConsistencyLevel.Session); - GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); + using GlobalEndpointManager endpointManager = new GlobalEndpointManager(mockDocumentClient.Object, new ConnectionPolicy()); SessionContainer sessionContainer = new SessionContainer(string.Empty); sessionContainer.SetSessionToken( @@ -818,8 +831,8 @@ private GatewayStoreModel GetGatewayStoreModelForConsistencyTest() DocumentClientEventSource eventSource = DocumentClientEventSource.Instance; HttpMessageHandler httpMessageHandler = new MockMessageHandler(messageHandler); - GatewayStoreModel storeModel = new GatewayStoreModel( - endpointManager, + using GatewayStoreModel storeModel = new GatewayStoreModel( + endpointManager, sessionContainer, ConsistencyLevel.Eventual, eventSource, @@ -830,7 +843,7 @@ private GatewayStoreModel GetGatewayStoreModelForConsistencyTest() PartitionKeyRangeCache partitionKeyRangeCache = new Mock(null, storeModel, clientCollectionCache).Object; storeModel.SetCaches(partitionKeyRangeCache, clientCollectionCache); - return storeModel; + await executeWithGatewayStoreModel(storeModel); } private async Task TestGatewayStoreModelProcessMessageAsync(GatewayStoreModel storeModel, DocumentServiceRequest request) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs index 95650faa6a..891917074d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs @@ -27,56 +27,104 @@ public class GlobalEndpointManagerTest [TestMethod] public async Task EndpointFailureMockTest() { - // Setup dummpy read locations for the database account - Collection readableLocations = new Collection(); + Environment.SetEnvironmentVariable("MinimumIntervalForNonForceRefreshLocationInMS", "100"); + try + { + // Setup dummpy read locations for the database account + Collection readableLocations = new Collection(); - AccountRegion writeLocation = new AccountRegion(); - writeLocation.Name = "WriteLocation"; - writeLocation.Endpoint = "https://writeendpoint.net/"; + AccountRegion writeLocation = new AccountRegion(); + writeLocation.Name = "WriteLocation"; + writeLocation.Endpoint = "https://writeendpoint.net/"; - AccountRegion readLocation1 = new AccountRegion(); - readLocation1.Name = "ReadLocation1"; - readLocation1.Endpoint = "https://readendpoint1.net/"; + AccountRegion readLocation1 = new AccountRegion(); + readLocation1.Name = "ReadLocation1"; + readLocation1.Endpoint = "https://readendpoint1.net/"; - AccountRegion readLocation2 = new AccountRegion(); - readLocation2.Name = "ReadLocation2"; - readLocation2.Endpoint = "https://readendpoint2.net/"; + AccountRegion readLocation2 = new AccountRegion(); + readLocation2.Name = "ReadLocation2"; + readLocation2.Endpoint = "https://readendpoint2.net/"; - readableLocations.Add(writeLocation); - readableLocations.Add(readLocation1); - readableLocations.Add(readLocation2); + readableLocations.Add(writeLocation); + readableLocations.Add(readLocation1); + readableLocations.Add(readLocation2); - AccountProperties databaseAccount = new AccountProperties(); - databaseAccount.ReadLocationsInternal = readableLocations; + AccountProperties databaseAccount = new AccountProperties(); + databaseAccount.ReadLocationsInternal = readableLocations; - //Setup mock owner "document client" - Mock mockOwner = new Mock(); - mockOwner.Setup(owner => owner.ServiceEndpoint).Returns(new Uri("https://defaultendpoint.net/")); - mockOwner.Setup(owner => owner.GetDatabaseAccountInternalAsync(It.IsAny(), It.IsAny())).ReturnsAsync(databaseAccount); + //Setup mock owner "document client" + Mock mockOwner = new Mock(); + mockOwner.Setup(owner => owner.ServiceEndpoint).Returns(new Uri("https://defaultendpoint.net/")); - //Create connection policy and populate preferred locations - ConnectionPolicy connectionPolicy = new ConnectionPolicy(); - connectionPolicy.PreferredLocations.Add("ReadLocation1"); - connectionPolicy.PreferredLocations.Add("ReadLocation2"); + int getAccountInfoCount = 0; + mockOwner.Setup(owner => owner.GetDatabaseAccountInternalAsync(It.IsAny(), It.IsAny())) + .Callback(() => getAccountInfoCount++) + .ReturnsAsync(databaseAccount); - GlobalEndpointManager globalEndpointManager = new GlobalEndpointManager(mockOwner.Object, connectionPolicy); + //Create connection policy and populate preferred locations + ConnectionPolicy connectionPolicy = new ConnectionPolicy(); + connectionPolicy.PreferredLocations.Add("ReadLocation1"); + connectionPolicy.PreferredLocations.Add("ReadLocation2"); - await globalEndpointManager.RefreshLocationAsync(databaseAccount); - Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); + using (GlobalEndpointManager globalEndpointManager = new GlobalEndpointManager(mockOwner.Object, connectionPolicy)) + { + globalEndpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(databaseAccount); + Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); - //Mark each of the read locations as unavailable and validate that the read endpoint switches to the next preferred region / default endpoint. - globalEndpointManager.MarkEndpointUnavailableForRead(globalEndpointManager.ReadEndpoints[0]); - globalEndpointManager.RefreshLocationAsync(null).Wait(); - Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation2.Endpoint)); + //Mark each of the read locations as unavailable and validate that the read endpoint switches to the next preferred region / default endpoint. + globalEndpointManager.MarkEndpointUnavailableForRead(globalEndpointManager.ReadEndpoints[0]); + await globalEndpointManager.RefreshLocationAsync(); + Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation2.Endpoint)); - globalEndpointManager.MarkEndpointUnavailableForRead(globalEndpointManager.ReadEndpoints[0]); - await globalEndpointManager.RefreshLocationAsync(null); - Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], globalEndpointManager.WriteEndpoints[0]); + globalEndpointManager.MarkEndpointUnavailableForRead(globalEndpointManager.ReadEndpoints[0]); + await globalEndpointManager.RefreshLocationAsync(); + Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], globalEndpointManager.WriteEndpoints[0]); - //Sleep a second for the unavailable endpoint entry to expire and background refresh timer to kick in - Thread.Sleep(3000); - await globalEndpointManager.RefreshLocationAsync(null); - Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); + getAccountInfoCount = 0; + //Sleep a second for the unavailable endpoint entry to expire and background refresh timer to kick in + await Task.Delay(TimeSpan.FromSeconds(3)); + Assert.IsTrue(getAccountInfoCount > 0, "Callback is not working. There should be at least one call in this time frame."); + + await globalEndpointManager.RefreshLocationAsync(); + Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); + } + + Assert.IsTrue(getAccountInfoCount > 0, "Callback is not working. There should be at least one call in this time frame."); + getAccountInfoCount = 0; + Thread.Sleep(TimeSpan.FromSeconds(3)); + Assert.AreEqual(0, getAccountInfoCount, "There should be no more account calls after the GlobalEndpointManager is disposed"); + } + finally + { + Environment.SetEnvironmentVariable("MinimumIntervalForNonForceRefreshLocationInMS", null); + } + } + + [TestMethod] + public async Task ValidateCancellationTokenLogicForGetDatabaseAccountFromAnyLocationAsync() + { + Uri defaultEndpoint = new Uri("https://testfailover.documents-test.windows-int.net/"); + using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.Cancel(); + + try + { + await GlobalEndpointManager.GetDatabaseAccountFromAnyLocationsAsync( + defaultEndpoint, + locations: new List(){ + "westus", + "southeastasia", + "northcentralus" + }, + getDatabaseAccountFn: (uri) => throw new Exception("The operation should be canceled and never make the network call."), + cancellationTokenSource.Token); + + Assert.Fail("Previous call should have failed"); + } + catch (OperationCanceledException op) + { + Assert.IsTrue(op.Message.Contains("GlobalEndpointManager")); + } } /// @@ -106,7 +154,8 @@ await GlobalEndpointManager.GetDatabaseAccountFromAnyLocationsAsync( } throw new Exception("This should never be hit since it should stop after the global endpoint hit the nonretriable exception"); - }); + }, + cancellationToken: default); Assert.Fail("Should throw the UnauthorizedException"); } @@ -136,7 +185,8 @@ await GlobalEndpointManager.GetDatabaseAccountFromAnyLocationsAsync( } throw new Microsoft.Azure.Documents.UnauthorizedException("Mock failed exception"); - }); + }, + cancellationToken: default); Assert.Fail("Should throw the UnauthorizedException"); } @@ -192,7 +242,8 @@ public async Task GetDatabaseAccountFromAnyLocationsMockTestAsync() "southeastasia", "northcentralus" }, - getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri)); + getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri), + cancellationToken: default); Assert.AreEqual(globalEndpointResult, databaseAccount); Assert.AreEqual(0, slowPrimaryRegionHelper.FailedEndpointCount); @@ -213,14 +264,14 @@ public async Task GetDatabaseAccountFromAnyLocationsMockTestAsync() "southeastasia", "northcentralus" }, - getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri)); + getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri), + cancellationToken: default); stopwatch.Stop(); Assert.AreEqual(globalEndpointResult, databaseAccount); Assert.AreEqual(2, slowPrimaryRegionHelper.SlowEndpointCount); Assert.IsTrue(slowPrimaryRegionHelper.ReturnedSuccess); - Assert.IsTrue(stopwatch.Elapsed > TimeSpan.FromSeconds(5)); - Assert.IsTrue(stopwatch.Elapsed < TimeSpan.FromSeconds(10)); + Assert.IsTrue(stopwatch.Elapsed > TimeSpan.FromSeconds(1)); } // All but the last URI succeeds @@ -236,7 +287,8 @@ public async Task GetDatabaseAccountFromAnyLocationsMockTestAsync() "southeastasia", "northcentralus" }, - getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri)); + getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri), + cancellationToken: default); Assert.AreEqual(globalEndpointResult, databaseAccount); Assert.AreEqual(3, slowPrimaryRegionHelper.FailedEndpointCount); @@ -256,7 +308,8 @@ public async Task GetDatabaseAccountFromAnyLocationsMockTestAsync() "southeastasia", "northcentralus" }, - getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri)); + getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri), + cancellationToken: default); Assert.AreEqual(globalEndpointResult, databaseAccount); Assert.AreEqual(0, slowPrimaryRegionHelper.FailedEndpointCount); @@ -280,7 +333,8 @@ public async Task GetDatabaseAccountFromAnyLocationsMockTestAsync() "westus6", "westus7", }, - getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri)); + getDatabaseAccountFn: (uri) => slowPrimaryRegionHelper.RequestHelper(uri), + cancellationToken: default); Assert.AreEqual(globalEndpointResult, databaseAccount); Assert.AreEqual(5, slowPrimaryRegionHelper.FailedEndpointCount); @@ -359,6 +413,9 @@ public void LocationHelperTest() [TestMethod] public void ReadLocationRemoveAndAddMockTest() { + string originalConfigValue = Environment.GetEnvironmentVariable("MinimumIntervalForNonForceRefreshLocationInMS"); + Environment.SetEnvironmentVariable("MinimumIntervalForNonForceRefreshLocationInMS", "1000"); + // Setup dummpy read locations for the database account Collection readableLocations = new Collection(); @@ -391,16 +448,16 @@ public void ReadLocationRemoveAndAddMockTest() connectionPolicy.PreferredLocations.Add("ReadLocation1"); connectionPolicy.PreferredLocations.Add("ReadLocation2"); - GlobalEndpointManager globalEndpointManager = new GlobalEndpointManager(mockOwner.Object, connectionPolicy); + using GlobalEndpointManager globalEndpointManager = new GlobalEndpointManager(mockOwner.Object, connectionPolicy); - globalEndpointManager.RefreshLocationAsync(databaseAccount).Wait(); + globalEndpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(databaseAccount); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); //Remove location 1 from read locations and validate that the read endpoint switches to the next preferred location readableLocations.Remove(readLocation1); databaseAccount.ReadLocationsInternal = readableLocations; - globalEndpointManager.RefreshLocationAsync(databaseAccount).Wait(); + globalEndpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(databaseAccount); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation2.Endpoint)); //Add location 1 back to read locations and validate that location 1 becomes the read endpoint again. @@ -410,6 +467,8 @@ public void ReadLocationRemoveAndAddMockTest() //Sleep a bit for the refresh timer to kick in and rediscover location 1 Thread.Sleep(2000); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); + + Environment.SetEnvironmentVariable("MinimumIntervalForNonForceRefreshLocationInMS", "1000"); } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs index 8559e1bb6e..f356344eb4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/HandlerTests.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System; using System.Collections; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -216,7 +217,8 @@ public async Task QueryRequestOptionsDedicatedGatewayRequestOptions() { TestHandler testHandler = new TestHandler((request, cancellationToken) => { - Assert.AreEqual(maxStaleness.ToString(), request.Headers[HttpConstants.HttpHeaders.DedicatedGatewayPerRequestCacheStaleness]); + Assert.AreEqual(maxStaleness.TotalMilliseconds.ToString(CultureInfo.InvariantCulture), request.Headers[HttpConstants.HttpHeaders.DedicatedGatewayPerRequestCacheStaleness]); + return TestHandler.ReturnSuccess(); }); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs index 4abb3fa7cc..5bd200ce40 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs @@ -44,7 +44,6 @@ public sealed class LocationCacheTests private ReadOnlyCollection preferredLocations; private AccountProperties databaseAccount; private LocationCache cache; - private GlobalEndpointManager endpointManager; private GlobalPartitionEndpointManager partitionKeyRangeLocationCache; private Mock mockedClient; @@ -52,7 +51,7 @@ public sealed class LocationCacheTests [Owner("atulk")] public void ValidateWriteEndpointOrderWithClientSideDisableMultipleWriteLocation() { - this.Initialize(false, true, false); + using GlobalEndpointManager endpointManager = this.Initialize(false, true, false); Assert.AreEqual(this.cache.WriteEndpoints[0], LocationCacheTests.Location1Endpoint); Assert.AreEqual(this.cache.WriteEndpoints[1], LocationCacheTests.Location2Endpoint); Assert.AreEqual(this.cache.WriteEndpoints[2], LocationCacheTests.Location3Endpoint); @@ -62,7 +61,7 @@ public void ValidateWriteEndpointOrderWithClientSideDisableMultipleWriteLocation [Owner("atulk")] public void ValidateGetLocation() { - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: false, enableEndpointDiscovery: true, isPreferredLocationsListEmpty: true); @@ -98,11 +97,11 @@ private async Task ValidateRetryOnSessionNotAvailabeWithEndpointDiscoveryDisable { const bool enableEndpointDiscovery = false; - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: useMultipleWriteLocations, enableEndpointDiscovery: enableEndpointDiscovery, isPreferredLocationsListEmpty: isPreferredLocationsListEmpty); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery, endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: isReadRequest, isMasterResourceType: false)) { @@ -117,7 +116,7 @@ await BackoffRetryUtility.ExecuteAsync( if (retryCount == 0) { - Assert.AreEqual(request.RequestContext.LocationEndpointToRoute, this.endpointManager.ReadEndpoints[0]); + Assert.AreEqual(request.RequestContext.LocationEndpointToRoute, endpointManager.ReadEndpoints[0]); } else { @@ -144,9 +143,15 @@ await BackoffRetryUtility.ExecuteAsync( } } - private ClientRetryPolicy CreateClientRetryPolicy(bool enableEndpointDiscovery) + private ClientRetryPolicy CreateClientRetryPolicy( + bool enableEndpointDiscovery, + GlobalEndpointManager endpointManager) { - return new ClientRetryPolicy(this.endpointManager, this.partitionKeyRangeLocationCache, enableEndpointDiscovery, new RetryOptions()); + return new ClientRetryPolicy( + endpointManager, + this.partitionKeyRangeLocationCache, + enableEndpointDiscovery, + new RetryOptions()); } [TestMethod] @@ -162,13 +167,13 @@ private async Task ValidateRetryOnSessionNotAvailabeWithDisableMultipleWriteLoca const bool useMultipleWriteLocations = false; bool enableEndpointDiscovery = true; - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: useMultipleWriteLocations, enableEndpointDiscovery: enableEndpointDiscovery, isPreferredLocationsListEmpty: isPreferredLocationsListEmpty); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery, endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: true, isMasterResourceType: false)) { @@ -239,14 +244,14 @@ private async Task ValidateRetryOnReadSessionNotAvailabeWithEnableMultipleWriteL "location1" }.AsReadOnly(); - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: useMultipleWriteLocations, enableEndpointDiscovery: enableEndpointDiscovery, isPreferredLocationsListEmpty: false, preferedRegionListOverride: preferredList); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery, endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: true, isMasterResourceType: false)) { @@ -315,14 +320,14 @@ private async Task ValidateRetryOnWriteSessionNotAvailabeWithEnableMultipleWrite "location1" }.AsReadOnly(); - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: useMultipleWriteLocations, enableEndpointDiscovery: enableEndpointDiscovery, isPreferredLocationsListEmpty: false, preferedRegionListOverride: preferredList); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery, endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: false, isMasterResourceType: false)) { @@ -388,13 +393,13 @@ await BackoffRetryUtility.ExecuteAsync( [Owner("atulk")] public async Task ValidateRetryOnWriteForbiddenExceptionAsync() { - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: false, enableEndpointDiscovery: true, isPreferredLocationsListEmpty: false); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(true); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery: true, endpointManager: endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: false, isMasterResourceType: false)) { @@ -460,13 +465,13 @@ public async Task ValidateRetryOnDatabaseAccountNotFoundAsync() private async Task ValidateRetryOnDatabaseAccountNotFoundAsync(bool enableMultipleWriteLocations, bool isReadRequest) { - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: enableMultipleWriteLocations, enableEndpointDiscovery: true, isPreferredLocationsListEmpty: false); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(true); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery: true, endpointManager: endpointManager); int expectedRetryCount = isReadRequest || enableMultipleWriteLocations ? 2 : 1; @@ -528,24 +533,23 @@ await BackoffRetryUtility.ExecuteAsync( } [TestMethod] - [Owner("atulk")] - public async Task ValidateAsync() + [DataRow(true, true, true)] + [DataRow(true, false, false)] + [DataRow(true, false, true)] + [DataRow(true, true, false)] + [DataRow(false, false, false)] + [DataRow(false, true, true)] + [DataRow(false, true, false)] + [DataRow(false, true, true)] + public async Task ValidateAsync( + bool useMultipleWriteEndpoints, + bool endpointDiscoveryEnabled, + bool isPreferredListEmpty) { - bool[] boolValues = new bool[] {true, false}; - - foreach (bool useMultipleWriteEndpoints in boolValues) - { - foreach (bool endpointDiscoveryEnabled in boolValues) - { - foreach (bool isPreferredListEmpty in boolValues) - { - await this.ValidateLocationCacheAsync( - useMultipleWriteEndpoints, - endpointDiscoveryEnabled, - isPreferredListEmpty); - } - } - } + await this.ValidateLocationCacheAsync( + useMultipleWriteEndpoints, + endpointDiscoveryEnabled, + isPreferredListEmpty); } [TestMethod] @@ -564,15 +568,15 @@ private async Task ValidateRetryOnHttpExceptionAsync(bool enableMultipleWriteLoc "location1" }.AsReadOnly(); - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: enableMultipleWriteLocations, enableEndpointDiscovery: true, isPreferredLocationsListEmpty: false, preferedRegionListOverride: preferredList, enforceSingleMasterSingleWriteLocation: true); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(true); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery: true, endpointManager: endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: isReadRequest, isMasterResourceType: false)) { @@ -642,19 +646,28 @@ await BackoffRetryUtility.ExecuteAsync( } [DataTestMethod] - [DataRow(true, false, false, false, DisplayName = "Read request - Single master - no preferred locations - should NOT retry")] - [DataRow(false, false, false, false, DisplayName = "Write request - Single master - no preferred locations - should NOT retry")] - [DataRow(true, true, false, false, DisplayName = "Read request - Multi master - no preferred locations - should NOT retry")] - [DataRow(false, true, false, false, DisplayName = "Write request - Multi master - no preferred locations - should NOT retry")] - [DataRow(true, false, true, true, DisplayName = "Read request - Single master - with preferred locations - should retry")] - [DataRow(false, false, true, false, DisplayName = "Write request - Single master - with preferred locations - should NOT retry")] - [DataRow(true, true, true, true, DisplayName = "Read request - Multi master - with preferred locations - should retry")] - [DataRow(false, true, true, true, DisplayName = "Write request - Multi master - with preferred locations - should retry")] + [DataRow(true, false, false, false, false, DisplayName = "Read request - Single master - no preferred locations - should NOT retry")] + [DataRow(false, false, false, false, false, DisplayName = "Write request - Single master - no preferred locations - should NOT retry")] + [DataRow(true, true, false, false, false, DisplayName = "Read request - Multi master - no preferred locations - should NOT retry")] + [DataRow(false, true, false, false, false, DisplayName = "Write request - Multi master - no preferred locations - should NOT retry")] + [DataRow(true, false, true, true, false, DisplayName = "Read request - Single master - with preferred locations - should retry")] + [DataRow(false, false, true, false, false, DisplayName = "Write request - Single master - with preferred locations - should NOT retry")] + [DataRow(true, true, true, true, false, DisplayName = "Read request - Multi master - with preferred locations - should retry")] + [DataRow(false, true, true, true, false, DisplayName = "Write request - Multi master - with preferred locations - should retry")] + [DataRow(true, false, false, false, true, DisplayName = "Read request - Single master - no preferred locations - should NOT retry")] + [DataRow(false, false, false, false, true, DisplayName = "Write request - Single master - no preferred locations - should NOT retry")] + [DataRow(true, true, false, false, true, DisplayName = "Read request - Multi master - no preferred locations - should NOT retry")] + [DataRow(false, true, false, false, true, DisplayName = "Write request - Multi master - no preferred locations - should NOT retry")] + [DataRow(true, false, true, true, true, DisplayName = "Read request - Single master - with preferred locations - should retry")] + [DataRow(false, false, true, false, true, DisplayName = "Write request - Single master - with preferred locations - should NOT retry")] + [DataRow(true, true, true, true, true, DisplayName = "Read request - Multi master - with preferred locations - should retry")] + [DataRow(false, true, true, true, true, DisplayName = "Write request - Multi master - with preferred locations - should retry")] public async Task ClientRetryPolicy_ValidateRetryOnServiceUnavailable( bool isReadRequest, bool useMultipleWriteLocations, bool usesPreferredLocations, - bool shouldHaveRetried) + bool shouldHaveRetried, + bool enablePartitionLevelFailover) { const bool enableEndpointDiscovery = true; @@ -663,15 +676,16 @@ public async Task ClientRetryPolicy_ValidateRetryOnServiceUnavailable( "location1" }.AsReadOnly(); - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations: useMultipleWriteLocations, enableEndpointDiscovery: enableEndpointDiscovery, isPreferredLocationsListEmpty: !usesPreferredLocations, + enablePartitionLevelFailover: enablePartitionLevelFailover, preferedRegionListOverride: preferredList, enforceSingleMasterSingleWriteLocation: true); - await this.endpointManager.RefreshLocationAsync(this.databaseAccount); - ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery); + endpointManager.InitializeAccountPropertiesAndStartBackgroundRefresh(this.databaseAccount); + ClientRetryPolicy retryPolicy = this.CreateClientRetryPolicy(enableEndpointDiscovery, endpointManager); using (DocumentServiceRequest request = this.CreateRequest(isReadRequest: isReadRequest, isMasterResourceType: false)) { @@ -760,12 +774,13 @@ private static AccountProperties CreateDatabaseAccount( return databaseAccount; } - private void Initialize( + private GlobalEndpointManager Initialize( bool useMultipleWriteLocations, bool enableEndpointDiscovery, bool isPreferredLocationsListEmpty, bool enforceSingleMasterSingleWriteLocation = false, // Some tests depend on the Initialize to create an account with multiple write locations, even when not multi master - ReadOnlyCollection preferedRegionListOverride = null) + ReadOnlyCollection preferedRegionListOverride = null, + bool enablePartitionLevelFailover = false) { this.databaseAccount = LocationCacheTests.CreateDatabaseAccount( useMultipleWriteLocations, @@ -810,8 +825,18 @@ private void Initialize( connectionPolicy.PreferredLocations.Add(preferredLocation); } - this.endpointManager = new GlobalEndpointManager(mockedClient.Object, connectionPolicy); - this.partitionKeyRangeLocationCache = GlobalPartitionEndpointManagerNoOp.Instance; + GlobalEndpointManager endpointManager = new GlobalEndpointManager(this.mockedClient.Object, connectionPolicy); + + if (enablePartitionLevelFailover) + { + this.partitionKeyRangeLocationCache = new GlobalPartitionEndpointManagerCore(endpointManager); + } + else + { + this.partitionKeyRangeLocationCache = GlobalPartitionEndpointManagerNoOp.Instance; + } + + return endpointManager; } private async Task ValidateLocationCacheAsync( @@ -823,7 +848,7 @@ private async Task ValidateLocationCacheAsync( { for (int readLocationIndex = 0; readLocationIndex < 2; readLocationIndex++) { - this.Initialize( + using GlobalEndpointManager endpointManager = this.Initialize( useMultipleWriteLocations, endpointDiscoveryEnabled, isPreferredListEmpty); @@ -834,13 +859,13 @@ private async Task ValidateLocationCacheAsync( for (int i = 0; i < readLocationIndex; i++) { this.cache.MarkEndpointUnavailableForRead(new Uri(this.databaseAccount.ReadLocationsInternal[i].Endpoint)); - this.endpointManager.MarkEndpointUnavailableForRead(new Uri(this.databaseAccount.ReadLocationsInternal[i].Endpoint)); + endpointManager.MarkEndpointUnavailableForRead(new Uri(this.databaseAccount.ReadLocationsInternal[i].Endpoint)); } for (int i = 0; i < writeLocationIndex; i++) { this.cache.MarkEndpointUnavailableForWrite(new Uri(this.databaseAccount.WriteLocationsInternal[i].Endpoint)); - this.endpointManager.MarkEndpointUnavailableForWrite( + endpointManager.MarkEndpointUnavailableForWrite( new Uri(this.databaseAccount.WriteLocationsInternal[i].Endpoint)); } @@ -871,7 +896,7 @@ private async Task ValidateLocationCacheAsync( currentWriteEndpoints.Count > 1, currentReadEndpoints.Count > 1); - await this.ValidateGlobalEndpointLocationCacheRefreshAsync(); + await this.ValidateGlobalEndpointLocationCacheRefreshAsync(endpointManager); this.ValidateRequestEndpointResolution( useMultipleWriteLocations, @@ -887,21 +912,21 @@ private async Task ValidateLocationCacheAsync( CultureInfo.InvariantCulture) * 1000 * 2; await Task.Delay(delayInMilliSeconds); - string config = $"Delay{expirationTime};" + + string config = $"Delay{expirationTime};" + $"useMultipleWriteLocations:{useMultipleWriteLocations};" + $"endpointDiscoveryEnabled:{endpointDiscoveryEnabled};" + $"isPreferredListEmpty:{isPreferredListEmpty}"; CollectionAssert.AreEqual( - currentWriteEndpoints, - this.cache.WriteEndpoints, + currentWriteEndpoints, + this.cache.WriteEndpoints, "Write Endpoints failed;" + $"config:{config};" + $"Current:{string.Join(",", currentWriteEndpoints)};" + $"Cache:{string.Join(",", this.cache.WriteEndpoints)};"); CollectionAssert.AreEqual( - currentReadEndpoints, + currentReadEndpoints, this.cache.ReadEndpoints, "Read Endpoints failed;" + $"config:{config};" + @@ -963,9 +988,9 @@ private void ValidateEndpointRefresh( } } - private async Task ValidateGlobalEndpointLocationCacheRefreshAsync() + private async Task ValidateGlobalEndpointLocationCacheRefreshAsync(GlobalEndpointManager endpointManager) { - IEnumerable refreshLocations = Enumerable.Range(0, 10).Select(index => Task.Factory.StartNew(() => this.endpointManager.RefreshLocationAsync(null))); + IEnumerable refreshLocations = Enumerable.Range(0, 10).Select(index => Task.Factory.StartNew(() => endpointManager.RefreshLocationAsync(false))); await Task.WhenAll(refreshLocations); @@ -973,7 +998,7 @@ private async Task ValidateGlobalEndpointLocationCacheRefreshAsync() this.mockedClient.ResetCalls(); - foreach (Task task in Enumerable.Range(0, 10).Select(index => Task.Factory.StartNew(() => this.endpointManager.RefreshLocationAsync(null)))) + foreach (Task task in Enumerable.Range(0, 10).Select(index => Task.Factory.StartNew(() => endpointManager.RefreshLocationAsync(false)))) { await task; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj index 546065cc82..93ac6e66c1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Microsoft.Azure.Cosmos.Tests.csproj @@ -34,6 +34,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -47,6 +83,7 @@ + @@ -58,6 +95,12 @@ all runtime; build; native; contentfiles; analyzers + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -349,6 +392,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ConcurrentHistogramTestBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ConcurrentHistogramTestBase.cs new file mode 100644 index 0000000000..1b987ead3b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ConcurrentHistogramTestBase.cs @@ -0,0 +1,34 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace HdrHistogram.UnitTests +{ + public abstract class ConcurrentHistogramTestBase : HistogramTestBase + { + [Fact] + public void Can_support_multiple_concurrent_recorders() + { + var target = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + const int loopcount = 10 * 1000 * 1000; + var concurrency = Environment.ProcessorCount; + var expected = loopcount * concurrency; + Action foo = () => + { + for (var i = 0; i < loopcount; i++) + target.RecordValue(i); + }; + + var actions = Enumerable.Range(1, concurrency) + .Select(_ => foo) + .ToArray(); + Parallel.Invoke(actions); + + Assert.Equal(expected, target.TotalCount); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HgrmPercentileDistrubutionOutputTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HgrmPercentileDistrubutionOutputTests.cs new file mode 100644 index 0000000000..196c1475f1 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HgrmPercentileDistrubutionOutputTests.cs @@ -0,0 +1,111 @@ +using System.Globalization; +using System.IO; +using System.Reflection; +using Xunit; + +namespace HdrHistogram.UnitTests +{ + /// + /// Validates against files generated by the official Java implementation. + /// + + public sealed class HgrmPercentileDistrubutionOutputTests + { + [Theory, CombinatorialData] + public void PercentileDistrubution_hgrm_output_is_in_correct_format( + [CombinatorialValues(36000000000)]long highestTrackableValue, + [CombinatorialValues(1, 2, 3, 4, 5)]int signigicantDigits, + [CombinatorialValues(10000.0)]double scaling, + [CombinatorialValues(5, 10, 20)]int percentileTicksPerHalfDistance) + { + var fileName = $"Sample_10kBy1k_{signigicantDigits}sf_TicksPerHour_asMs_{percentileTicksPerHalfDistance}percPerHalfDistance.hgrm"; + var expected = GetEmbeddedFileText(fileName); + + var histogram = new LongHistogram(highestTrackableValue, signigicantDigits); + LoadHistogram(histogram); + + var writer = new StringWriter(); + histogram.OutputPercentileDistribution(writer, percentileTicksPerHalfDistance, scaling); + var actual = writer.ToString(); + + Assert.Equal(expected, actual); + } + + [Theory, CombinatorialData] + public void PercentileDistrubution_csv_output_is_in_correct_format( + [CombinatorialValues(36000000000)]long highestTrackableValue, + [CombinatorialValues(1, 2, 3, 4, 5)]int signigicantDigits, + [CombinatorialValues(10000.0)]double scaling, + [CombinatorialValues(5, 10, 20)]int percentileTicksPerHalfDistance) + { + var fileName = $"Sample_10kBy1k_{signigicantDigits}sf_TicksPerHour_asMs_{percentileTicksPerHalfDistance}percPerHalfDistance.csv"; + var expected = GetEmbeddedFileText(fileName); + + var histogram = new LongHistogram(highestTrackableValue, signigicantDigits); + LoadHistogram(histogram); + + var writer = new StringWriter(); + histogram.OutputPercentileDistribution(writer, percentileTicksPerHalfDistance, scaling, true); + var actual = writer.ToString(); + + Assert.Equal(expected, actual); + } + + [Fact] //BUG https://github.com/HdrHistogram/HdrHistogram.NET/issues/39 + public void OnlySingleValueFlaggedAsLastValue() + { + var expected = GetEmbeddedFileText("IsLastValueBug.hgrm"); + + var histogram = new LongHistogram(highestTrackableValue: 36000000000, numberOfSignificantValueDigits:3); + histogram.RecordValueWithCount(1L, 7604459); + histogram.RecordValueWithCount(383, 2395524); + histogram.RecordValueWithCount(453, 2); + histogram.RecordValueWithCount(511, 2); + histogram.RecordValueWithCount(537, 3); + histogram.RecordValueWithCount(672, 1); + histogram.RecordValueWithCount(777, 1); + histogram.RecordValueWithCount(18143, 1); + histogram.RecordValueWithCount(208127, 1); + histogram.RecordValueWithCount(224639, 1); + histogram.RecordValueWithCount(229759, 1); + histogram.RecordValueWithCount(230271, 1); + histogram.RecordValueWithCount(258943, 1); + histogram.RecordValueWithCount(275711, 1); + histogram.RecordValueWithCount(282111, 1); + + var writer = new StringWriter(); + histogram.OutputPercentileDistribution(writer); + var actual = writer.ToString(); + + Assert.Equal(expected, actual); + } + + private Stream GetEmbeddedFileStream(string filename) + { + var fileName = string + .Format(CultureInfo.InvariantCulture, + "Microsoft.Azure.Cosmos.Tests.OSS.HdrHistogram.Resources.{0}", filename); + + return GetType().GetTypeInfo() + .Assembly + .GetManifestResourceStream(fileName); + } + + private string GetEmbeddedFileText(string filename) + { + using (var stream = GetEmbeddedFileStream(filename)) + { + var reader = new StreamReader(stream); + return reader.ReadToEnd().Replace("\r\n", "\n"); + } + } + + private static void LoadHistogram(IRecorder histogram) + { + for (int i = 0; i < 10000; i += 1000) + { + histogram.RecordValue(i); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramAssert.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramAssert.cs new file mode 100644 index 0000000000..5303355387 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramAssert.cs @@ -0,0 +1,58 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Linq; +using FluentAssertions; +using HdrHistogram.Iteration; +using Xunit; + +namespace HdrHistogram.UnitTests +{ + internal static class HistogramAssert + { + public static void AreEqual(HistogramBase expected, HistogramBase actual) + { + Assert.Equal(expected.GetType(), actual.GetType()); + AreValueEqual(expected, actual); + } + + public static void AreValueEqual(HistogramBase expected, HistogramBase actual) + { + expected.TotalCount.Should().Be(actual.TotalCount, "TotalCount property is not equal."); + expected.Tag.Should().Be(actual.Tag, "Tag property is not equal."); + expected.StartTimeStamp.Should().Be(actual.StartTimeStamp, "StartTimeStamp property is not equal."); + expected.EndTimeStamp.Should().Be(actual.EndTimeStamp, "EndTimeStamp property is not equal."); + expected.LowestTrackableValue.Should().Be(actual.LowestTrackableValue, "LowestTrackableValue property is not equal."); + expected.HighestTrackableValue.Should().Be(actual.HighestTrackableValue, "HighestTrackableValue property is not equal."); + expected.NumberOfSignificantValueDigits.Should().Be(actual.NumberOfSignificantValueDigits, "NumberOfSignificantValueDigits property is not equal."); + + var expectedValues = expected.AllValues().ToArray(); + var actualValues = actual.AllValues().ToArray(); + CollectionEquals(expectedValues, actualValues); + } + + private static void CollectionEquals(HistogramIterationValue[] expected, HistogramIterationValue[] actual) + { + if (expected == null && actual == null) + return; + if(expected == null) + throw new Exception("Expected null array"); + if (actual == null) + throw new Exception("Unexpected null array"); + + if(expected.Length != actual.Length) + throw new Exception($"Expected length of {expected.Length}, but recieved {actual.Length}"); + + for (int i = 0; i < expected.Length; i++) + { + var e = expected[i]; + var a = actual[i]; + if (HistogramIterationValueComparer.Instance.Compare(e, a) != 0) + { + throw new Exception($"Values differ at index {i}. Expected {e}, but recieved {a}"); + } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramEncodingTestBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramEncodingTestBase.cs new file mode 100644 index 0000000000..8586aa4c4d --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramEncodingTestBase.cs @@ -0,0 +1,90 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using HdrHistogram.Encoding; +using HdrHistogram.UnitTests; +using HdrHistogram.Utilities; +using Xunit; + +namespace HdrHistogram +{ + public abstract class HistogramEncodingTestBase + { + protected const long DefautltLowestDiscernibleValue = 1; + protected const long DefaultHighestTrackableValue = 7716549600;//TimeStamp.Hours(1); // e.g. for 1 hr in system clock ticks (StopWatch.Frequency) + protected const int DefaultSignificantFigures = 3; + + private static readonly HistogramEncoderV2 EncoderV2 = new Encoding.HistogramEncoderV2(); + + [Fact] + public void Given_a_populated_Histogram_When_encoded_and_decoded_Then_data_is_preserved() + { + var source = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Load(source); + var result = EncodeDecode(source); + HistogramAssert.AreValueEqual(source, result); + } + + [Fact] + public void Given_a_populated_Histogram_When_encoded_and_decoded_with_compression_Then_data_is_preserved() + { + var source = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Load(source); + var result = CompressedEncodeDecode(source); + HistogramAssert.AreValueEqual(source, result); + } + + [Fact] + public void Given_a_Histogram_populated_with_full_range_of_values_When_encoded_and_decoded_Then_data_is_preserved() + { + var source = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + LoadFullRange(source); + var result = EncodeDecode(source); + HistogramAssert.AreValueEqual(source, result); + } + + [Fact] + public void Given_a_Histogram_populated_with_full_range_of_values_When_encoded_and_decoded_with_compression_Then_data_is_preserved() + { + var source = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + LoadFullRange(source); + var result = CompressedEncodeDecode(source); + HistogramAssert.AreValueEqual(source, result); + } + + internal abstract HistogramBase Create(long highestTrackableValue, int numberOfSignificantDigits); + + private static HistogramBase EncodeDecode(HistogramBase source) + { + var targetBuffer = ByteBuffer.Allocate(source.GetNeededByteBufferCapacity()); + source.Encode(targetBuffer, EncoderV2); + targetBuffer.Position = 0; + return HistogramEncoding.DecodeFromByteBuffer(targetBuffer, 0); + } + + private static HistogramBase CompressedEncodeDecode(HistogramBase source) + { + var targetBuffer = ByteBuffer.Allocate(source.GetNeededByteBufferCapacity()); + source.EncodeIntoCompressedByteBuffer(targetBuffer); + targetBuffer.Position = 0; + return HistogramEncoding.DecodeFromCompressedByteBuffer(targetBuffer, 0); + } + + private static void Load(IRecorder source) + { + for (long i = 0L; i < 10000L; i++) + { + source.RecordValue(1000L * i); + } + } + + internal virtual void LoadFullRange(IRecorder source) + { + for (long i = 0L; i < DefaultHighestTrackableValue; i += 100L) + { + source.RecordValue(i); + } + source.RecordValue(DefaultHighestTrackableValue); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramFactoryTests.cs new file mode 100644 index 0000000000..c9b81deb1c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramFactoryTests.cs @@ -0,0 +1,235 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + public class HistogramFactoryTests + { + #region 16bit recording factory tests + + [Fact] + public void CanCreateShortHistogram() + { + var actual = HistogramFactory.With16BitBucketSize() + .Create(); + Assert.IsAssignableFrom(actual); + } + + [Theory] + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateShortHistogramWithSpecifiedRangeValues(long min, long max, int sf) + { + var actual = HistogramFactory.With16BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .Create(); + Assert.IsAssignableFrom(actual); + Assert.Equal(min, actual.LowestTrackableValue); + Assert.Equal(max, actual.HighestTrackableValue); + Assert.Equal(sf, actual.NumberOfSignificantValueDigits); + } + + [Theory] + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateShortHistogramRecorder(long min, long max, int sf) + { + var actual = HistogramFactory.With16BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeReads() + .Create(); + var histogram = actual.GetIntervalHistogram(); + Assert.IsAssignableFrom(histogram); + Assert.Equal(min, histogram.LowestTrackableValue); + Assert.Equal(max, histogram.HighestTrackableValue); + Assert.Equal(sf, histogram.NumberOfSignificantValueDigits); + } + + #endregion + + #region 32bit recording factory tests + + [Fact] + public void CanCreateIntHistogram() + { + var actual = HistogramFactory.With32BitBucketSize() + .Create(); + Assert.IsAssignableFrom(actual); + } + [Fact] + public void CanCreateIntConcurrentHistogram() + { + var actual = HistogramFactory.With32BitBucketSize() + .WithThreadSafeWrites() + .Create(); + Assert.IsAssignableFrom(actual); + } + + [Theory] + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + + public void CanCreateIntHistogramWithSpecifiedRangeValues(long min, long max, int sf) + { + var actual = HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .Create(); + Assert.IsAssignableFrom(actual); + Assert.Equal(min, actual.LowestTrackableValue); + Assert.Equal(max, actual.HighestTrackableValue); + Assert.Equal(sf, actual.NumberOfSignificantValueDigits); + } + [Theory] + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void IntConcurrentHistogramWithSpecifiedRangeValues(long min, long max, int sf) + { + var actual = HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + + .Create(); + Assert.IsAssignableFrom(actual); + Assert.Equal(min, actual.LowestTrackableValue); + + Assert.Equal(max, actual.HighestTrackableValue); + Assert.Equal(sf, actual.NumberOfSignificantValueDigits); + } + +[Theory] + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateIntHistogramRecorder(long min, long max, int sf) + + { + var actual = HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeReads() + .Create(); + var histogram = actual.GetIntervalHistogram(); + Assert.IsAssignableFrom(histogram); + Assert.Equal(min, histogram.LowestTrackableValue); + Assert.Equal(max, histogram.HighestTrackableValue); + Assert.Equal(sf, histogram.NumberOfSignificantValueDigits); + } + +[Theory] + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateIntConcurrentHistogramRecorder(long min, long max, int sf) + { + var actual = HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .WithThreadSafeReads() + .Create(); + var histogram = actual.GetIntervalHistogram(); + Assert.IsAssignableFrom(histogram); + Assert.Equal(min, histogram.LowestTrackableValue); + Assert.Equal(max, histogram.HighestTrackableValue); + Assert.Equal(sf, histogram.NumberOfSignificantValueDigits); + } + + #endregion + + #region 64bit recording factory tests + + [Fact] + public void CanCreateLongHistogram() + { + var actual = HistogramFactory.With64BitBucketSize() + .Create(); + Assert.IsAssignableFrom(actual); + } + [Fact] + public void CanCreateLongConcurrentHistogram() + { + var actual = HistogramFactory.With64BitBucketSize() + .WithThreadSafeWrites() + .Create(); + Assert.IsAssignableFrom(actual); + } + + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateLongHistogramWithSpecifiedRangeValues(long min, long max, int sf) + { + var actual = HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .Create(); + Assert.IsAssignableFrom(actual); + Assert.Equal(min, actual.LowestTrackableValue); + Assert.Equal(max, actual.HighestTrackableValue); + Assert.Equal(sf, actual.NumberOfSignificantValueDigits); + } + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void LongConcurrentHistogramWithSpecifiedRangeValues(long min, long max, int sf) + { + var actual = HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .Create(); + Assert.IsAssignableFrom(actual); + Assert.Equal(min, actual.LowestTrackableValue); + Assert.Equal(max, actual.HighestTrackableValue); + Assert.Equal(sf, actual.NumberOfSignificantValueDigits); + } + + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateLongHistogramRecorder(long min, long max, int sf) + { + var actual = HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeReads() + .Create(); + var histogram = actual.GetIntervalHistogram(); + Assert.IsAssignableFrom(histogram); + Assert.Equal(min, histogram.LowestTrackableValue); + Assert.Equal(max, histogram.HighestTrackableValue); + Assert.Equal(sf, histogram.NumberOfSignificantValueDigits); + } + + [InlineData(1, 5000, 3)] + [InlineData(1000, 100000, 5)] + public void CanCreateLongConcurrentHistogramRecorder(long min, long max, int sf) + { + var actual = HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .WithThreadSafeReads() + .Create(); + var histogram = actual.GetIntervalHistogram(); + Assert.IsAssignableFrom(histogram); + Assert.Equal(min, histogram.LowestTrackableValue); + Assert.Equal(max, histogram.HighestTrackableValue); + Assert.Equal(sf, histogram.NumberOfSignificantValueDigits); + } + + #endregion + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramIterationValueComparer.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramIterationValueComparer.cs new file mode 100644 index 0000000000..ff6f4e998f --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramIterationValueComparer.cs @@ -0,0 +1,45 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections; +using System.Collections.Generic; +using HdrHistogram.Iteration; + +namespace HdrHistogram.UnitTests +{ + internal sealed class HistogramIterationValueComparer : IComparer, IComparer + { + public static readonly HistogramIterationValueComparer Instance = new HistogramIterationValueComparer(); + + public int Compare(HistogramIterationValue x, HistogramIterationValue other) + { + var result = x.ValueIteratedTo.CompareTo(other.ValueIteratedTo); + if (result != 0) + return result; + result = x.ValueIteratedFrom.CompareTo(other.ValueIteratedFrom); + if (result != 0) + return result; + result = x.CountAtValueIteratedTo.CompareTo(other.CountAtValueIteratedTo); + if (result != 0) + return result; + result = x.CountAddedInThisIterationStep.CompareTo(other.CountAddedInThisIterationStep); + if (result != 0) + return result; + result = x.TotalCountToThisValue.CompareTo(other.TotalCountToThisValue); + if (result != 0) + return result; + result = x.TotalValueToThisValue.CompareTo(other.TotalValueToThisValue); + if (result != 0) + return result; + result = x.Percentile.CompareTo(other.Percentile); + if (result != 0) + return result; + return x.PercentileLevelIteratedTo.CompareTo(other.PercentileLevelIteratedTo); + } + + public int Compare(object x, object y) + { + return Compare((HistogramIterationValue)x, (HistogramIterationValue)y); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramTestBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramTestBase.cs new file mode 100644 index 0000000000..4157ead61c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/HistogramTestBase.cs @@ -0,0 +1,398 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using FluentAssertions; +using Xunit; + +namespace HdrHistogram.UnitTests +{ + public abstract class HistogramTestBase + { + protected const long DefautltLowestDiscernibleValue = 1; + protected const long DefaultHighestTrackableValue = 7716549600;//TimeStamp.Hours(1); // e.g. for 1 hr in system clock ticks (StopWatch.Frequency) + protected const int DefaultSignificantFigures = 3; + protected const long TestValueLevel = 4; + + private static readonly IDictionary> WordSizeToFactory = + new Dictionary>() + { + { 2, (low, high, sf) => new ShortHistogram(low, high, sf) }, + { 4, (low,high,sf) => new IntHistogram(low, high, sf) }, + { 8, (low,high,sf) => new LongHistogram(low, high, sf) } + }; + + protected abstract int WordSize { get; } + internal abstract HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits); + internal abstract HistogramBase Create(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits); + + [Theory] + [InlineData(0, 1, DefaultSignificantFigures, "lowestTrackableValue", "lowestTrackableValue must be >= 1")] + [InlineData(DefautltLowestDiscernibleValue, 1, DefaultSignificantFigures, "highestTrackableValue", "highestTrackableValue must be >= 2 * lowestTrackableValue")] + [InlineData(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, 6, "numberOfSignificantValueDigits", "numberOfSignificantValueDigits must be between 0 and 5")] + [InlineData(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, -1, "numberOfSignificantValueDigits", "numberOfSignificantValueDigits must be between 0 and 5")] + public void ConstructorShouldRejectInvalidParameters( + long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits, + string errorParamName, string errorMessage) + { + var ex = Assert.Throws(() => { Create(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); }); + Assert.Equal(errorParamName, ex.ParamName); + Assert.StartsWith(errorMessage, ex.Message); + } + + [Theory] + [InlineData(2, 2)] + [InlineData(DefaultHighestTrackableValue, DefaultSignificantFigures)] + public void Test2ConstructionArgumentGets(long highestTrackableValue, int numberOfSignificantValueDigits) + { + var histogram = Create(highestTrackableValue, numberOfSignificantValueDigits); + Assert.Equal(1, histogram.LowestTrackableValue); + Assert.Equal(highestTrackableValue, histogram.HighestTrackableValue); + Assert.Equal(numberOfSignificantValueDigits, histogram.NumberOfSignificantValueDigits); + } + + [Theory] + [InlineData(1, 2, 2)] + [InlineData(10, DefaultHighestTrackableValue, DefaultSignificantFigures)] + public void Test3ConstructionArgumentGets(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + var histogram = Create(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); + Assert.Equal(lowestTrackableValue, histogram.LowestTrackableValue); + Assert.Equal(highestTrackableValue, histogram.HighestTrackableValue); + Assert.Equal(numberOfSignificantValueDigits, histogram.NumberOfSignificantValueDigits); + } + + [Fact] + public void TestGetEstimatedFootprintInBytes2() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + var largestValueWithSingleUnitResolution = 2 * (long)Math.Pow(10, DefaultSignificantFigures); + var subBucketCountMagnitude = (int)Math.Ceiling(Math.Log(largestValueWithSingleUnitResolution) / Math.Log(2)); + var subBucketSize = (int)Math.Pow(2, (subBucketCountMagnitude)); + var bucketCount = GetBucketsNeededToCoverValue(subBucketSize, DefaultHighestTrackableValue); + + var header = 512; + var width = WordSize; + var length = (bucketCount + 1) * (subBucketSize / 2); + var expectedSize = header + (width * length); + + Assert.Equal(expectedSize, histogram.GetEstimatedFootprintInBytes()); + } + + + [Fact] + public void RecordValue_increments_TotalCount() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + for (int i = 1; i < 5; i++) + { + histogram.RecordValue(i); + Assert.Equal(i, histogram.TotalCount); + } + } + + [Fact] + public void RecordValue_increments_CountAtValue() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + for (int i = 1; i < 5; i++) + { + histogram.RecordValue(TestValueLevel); + Assert.Equal(i, histogram.GetCountAtValue(TestValueLevel)); + } + } + + [Fact] + public void RecordValue_Overflow_ShouldThrowException() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Throws(() => histogram.RecordValue(DefaultHighestTrackableValue * 3)); + } + + [Theory] + [InlineData(5)] + [InlineData(100)] + public void RecordValueWithCount_increments_TotalCount(long multiplier) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + for (int i = 1; i < 5; i++) + { + histogram.RecordValueWithCount(i, multiplier); + Assert.Equal(i * multiplier, histogram.TotalCount); + } + } + + [Theory] + [InlineData(5)] + [InlineData(100)] + public void RecordValueWithCount_increments_CountAtValue(long multiplier) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + for (int i = 1; i < 5; i++) + { + histogram.RecordValueWithCount(TestValueLevel, multiplier); + Assert.Equal(i * multiplier, histogram.GetCountAtValue(TestValueLevel)); + } + } + + [Fact] + public void RecordValueWithCount_Overflow_ShouldThrowException() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Throws(() => histogram.RecordValueWithCount(DefaultHighestTrackableValue * 3, 10)); + } + + + [Fact] + public void RecordValueWithExpectedInterval() + { + var intervalHistogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + var valueHistogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + + intervalHistogram.RecordValueWithExpectedInterval(TestValueLevel, TestValueLevel / 4); + valueHistogram.RecordValue(TestValueLevel); + + // The data will include corrected samples: + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 1) / 4)); + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 2) / 4)); + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 3) / 4)); + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 4) / 4)); + Assert.Equal(4L, intervalHistogram.TotalCount); + // But the raw data will not: + Assert.Equal(0L, valueHistogram.GetCountAtValue((TestValueLevel * 1) / 4)); + Assert.Equal(0L, valueHistogram.GetCountAtValue((TestValueLevel * 2) / 4)); + Assert.Equal(0L, valueHistogram.GetCountAtValue((TestValueLevel * 3) / 4)); + Assert.Equal(1L, valueHistogram.GetCountAtValue((TestValueLevel * 4) / 4)); + Assert.Equal(1L, valueHistogram.TotalCount); + } + + [Fact] + public void Record_increments_TotalCount() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + + histogram.Record(() => { }); + Assert.Equal(1, histogram.TotalCount); + } + + [Fact] + public void Record_records_in_correct_units() + { + var pause = TimeSpan.FromSeconds(1); + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + + histogram.Record(() => + { + Spin.Wait(TimeSpan.FromSeconds(1)); + }); + + var stringWriter = new StringWriter(); + histogram.OutputPercentileDistribution(stringWriter, + percentileTicksPerHalfDistance: 5, + outputValueUnitScalingRatio: OutputScalingFactor.TimeStampToMilliseconds, + useCsvFormat: true); + + //First column of second row. + var recordedMilliseconds = GetCellValue(stringWriter.ToString(), 0, 1); + var actual = double.Parse(recordedMilliseconds); + var expected = pause.TotalMilliseconds; + + //10% Variance to allow for slack in transitioning from Thread.Sleep + actual.Should().BeInRange(expected * 0.9, expected * 1.1); + } + + [Fact] + public void Reset_sets_counts_to_zero() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + histogram.RecordValue(TestValueLevel); + + histogram.Reset(); + + Assert.Equal(0L, histogram.GetCountAtValue(TestValueLevel)); + Assert.Equal(0L, histogram.TotalCount); + } + + + [Fact] + public void Add_should_sum_the_counts_from_two_histograms() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + var other = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + histogram.RecordValue(TestValueLevel); + histogram.RecordValue(TestValueLevel * 1000); + other.RecordValue(TestValueLevel); + other.RecordValue(TestValueLevel * 1000); + + histogram.Add(other); + + Assert.Equal(2L, histogram.GetCountAtValue(TestValueLevel)); + Assert.Equal(2L, histogram.GetCountAtValue(TestValueLevel * 1000)); + Assert.Equal(4L, histogram.TotalCount); + } + + [Fact] + public void Add_should_allow_small_range_hsitograms_to_be_added() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + + histogram.RecordValue(TestValueLevel); + histogram.RecordValue(TestValueLevel * 1000); + + var biggerOther = Create(DefaultHighestTrackableValue * 2, DefaultSignificantFigures); + biggerOther.RecordValue(TestValueLevel); + biggerOther.RecordValue(TestValueLevel * 1000); + + // Adding the smaller histogram to the bigger one should work: + biggerOther.Add(histogram); + Assert.Equal(2L, biggerOther.GetCountAtValue(TestValueLevel)); + Assert.Equal(2L, biggerOther.GetCountAtValue(TestValueLevel * 1000)); + Assert.Equal(4L, biggerOther.TotalCount); + } + + [Fact] + public void Add_throws_if_other_has_a_larger_range() + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + var biggerOther = Create(DefaultHighestTrackableValue * 2, DefaultSignificantFigures); + + Assert.Throws(() => { histogram.Add(biggerOther); }); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(2, 2500)] + [InlineData(4, 8191)] + [InlineData(8, 8192)] + [InlineData(8, 10000)] + public void SizeOfEquivalentValueRangeForValue(int expected, int value) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected, histogram.SizeOfEquivalentValueRange(value)); + //Validate the scaling too. + + var scaledHistogram = Create(1024, DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected * 1024, scaledHistogram.SizeOfEquivalentValueRange(value * 1024)); + } + + [Theory] + [InlineData(10000, 10007)] + [InlineData(10008, 10009)] + public void LowestEquivalentValue_returns_the_smallest_value_that_would_be_assigned_to_the_same_count(int expected, int value) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected, histogram.LowestEquivalentValue(value)); + //Validate the scaling too + var scaledHistogram = Create(1024, DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected * 1024, scaledHistogram.LowestEquivalentValue(value * 1024)); + } + + [Theory] + [InlineData(8183, 8180)] + [InlineData(8191, 8191)] + [InlineData(8199, 8193)] + [InlineData(9999, 9995)] + [InlineData(10007, 10007)] + [InlineData(10015, 10008)] + public void HighestEquivalentValue_returns_the_smallest_value_that_would_be_assigned_to_the_same_count(int expected, int value) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected, histogram.HighestEquivalentValue(value)); + //Validate the scaling too + var scaledHistogram = Create(1024, DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected * 1024 + 1023, scaledHistogram.HighestEquivalentValue(value * 1024)); + } + + [Theory] + [InlineData(4, 4, 512)] + [InlineData(5, 5, 512)] + [InlineData(4001, 4000, 0)] + [InlineData(8002, 8000, 0)] + [InlineData(10004, 10007, 0)] + public void TestMedianEquivalentValue(int expected, int value, int scaledHeader) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected, histogram.MedianEquivalentValue(value)); + //Validate the scaling too + var scaledHistogram = Create(1024, DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Equal(expected * 1024 + scaledHeader, scaledHistogram.MedianEquivalentValue(value * 1024)); + } + + + [Fact] + public void When_more_items_are_recorded_than_totalCount_can_hold_Then_set_HasOverflowed_to_True() + { + var histogram = Create(DefaultHighestTrackableValue, 2); + Assert.False(histogram.HasOverflowed()); + + histogram.RecordValueWithCount(TestValueLevel, long.MaxValue); + histogram.RecordValueWithCount(TestValueLevel * 1024, long.MaxValue); + + Assert.True(histogram.HasOverflowed()); + } + + [Fact] + public void Can_add_Histograms_with_larger_wordSize_when_values_are_in_range() + { + var largerHistogramFactory = WordSizeToFactory.Where(kvp => kvp.Key >= WordSize).Select(kvp => kvp.Value); + foreach (var sourceFactory in largerHistogramFactory) + { + CreateAndAdd(sourceFactory(1, DefaultHighestTrackableValue, DefaultSignificantFigures)); + } + } + + [Fact] + public void Copy_retains_all_public_properties() + { + var source = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + var copy = source.Copy(); + HistogramAssert.AreValueEqual(source, copy); + } + + [Theory] + [InlineData("No spaces")] + [InlineData("No,commas")] + [InlineData("\r")] + [InlineData("\n")] + [InlineData(" ")] + [InlineData(" ")] + public void Setting_invalid_value_to_Tag_throws(string invalidTagValue) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantFigures); + Assert.Throws(() => histogram.Tag = invalidTagValue); + } + + private void CreateAndAdd(HistogramBase source) + { + source.RecordValueWithCount(1, 100); + source.RecordValueWithCount(int.MaxValue - 1, 1000); + + var target = Create(source.LowestTrackableValue, source.HighestTrackableValue, source.NumberOfSignificantValueDigits); + target.Add(source); + + HistogramAssert.AreValueEqual(source, target); + } + + private static int GetBucketsNeededToCoverValue(int subBucketSize, long value) + { + long trackableValue = (subBucketSize - 1);// << _unitMagnitude; + int bucketsNeeded = 1; + while (trackableValue < value) + { + trackableValue <<= 1; + bucketsNeeded++; + } + return bucketsNeeded; + } + + private static string GetCellValue(string csvData, int col, int row) + { + return csvData.Split('\n')[row].Split(',')[col]; + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntConcurrentHistogramTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntConcurrentHistogramTests.cs new file mode 100644 index 0000000000..a71ef13698 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntConcurrentHistogramTests.cs @@ -0,0 +1,34 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal class IntConcurrentHistogramTests : ConcurrentHistogramTestBase + { + protected override int WordSize => sizeof(int); + + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new IntConcurrentHistogram(1, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With32BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .WithThreadSafeWrites() + .Create(); + } + + internal override HistogramBase Create(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new IntConcurrentHistogram(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With32BitBucketSize() + .WithValuesFrom(lowestTrackableValue) + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .WithThreadSafeWrites() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntHistogramEncodingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntHistogramEncodingTests.cs new file mode 100644 index 0000000000..027b1f2c1e --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntHistogramEncodingTests.cs @@ -0,0 +1,20 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal sealed class IntHistogramEncodingTests : HistogramEncodingTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantDigits) + { + //return new IntHistogram(highestTrackableValue, numberOfSignificantDigits); + return HistogramFactory.With32BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantDigits) + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntHistogramTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntHistogramTests.cs new file mode 100644 index 0000000000..712dfcc8cc --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/IntHistogramTests.cs @@ -0,0 +1,32 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal class IntHistogramTests : HistogramTestBase + { + protected override int WordSize => sizeof(int); + + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new IntHistogram(highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With32BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .Create(); + } + + internal override HistogramBase Create(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new IntHistogram(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With32BitBucketSize() + .WithValuesFrom(lowestTrackableValue) + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongConcurrentHistogramTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongConcurrentHistogramTests.cs new file mode 100644 index 0000000000..f6335b1d7b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongConcurrentHistogramTests.cs @@ -0,0 +1,34 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal class LongConcurrentHistogramTests : ConcurrentHistogramTestBase + { + protected override int WordSize => sizeof(long); + + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new LongConcurrentHistogram(1, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With64BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .WithThreadSafeWrites() + .Create(); + } + + internal override HistogramBase Create(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new LongConcurrentHistogram(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With64BitBucketSize() + .WithValuesFrom(lowestTrackableValue) + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .WithThreadSafeWrites() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongHistogramEncodingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongHistogramEncodingTests.cs new file mode 100644 index 0000000000..24b862db50 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongHistogramEncodingTests.cs @@ -0,0 +1,20 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal sealed class LongHistogramEncodingTests : HistogramEncodingTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantDigits) + { + //return new LongHistogram(highestTrackableValue, numberOfSignificantDigits); + return HistogramFactory.With64BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantDigits) + .Create(); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongHistogramTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongHistogramTests.cs new file mode 100644 index 0000000000..fdfe1e84f8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/LongHistogramTests.cs @@ -0,0 +1,30 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal class LongHistogramTests : HistogramTestBase + { + protected override int WordSize => sizeof(long); + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new LongHistogram(highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With64BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .Create(); + } + internal override HistogramBase Create(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new LongHistogram(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With64BitBucketSize() + .WithValuesFrom(lowestTrackableValue) + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .Create(); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/MathEx.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/MathEx.cs new file mode 100644 index 0000000000..09b7e9ffbe --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/MathEx.cs @@ -0,0 +1,23 @@ +using System; + +namespace HdrHistogram.UnitTests +{ + public static class MathEx + { + public static double Round(this double value, int digits) + { + if (digits >= 0) + { + return Math.Round(value, digits); + } + else + { + digits = Math.Abs(digits); + var temp = value / Math.Pow(10, digits); + temp = Math.Round(temp, 0); + temp = temp * Math.Pow(10, digits); + return temp; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/HistogramLogExtensions.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/HistogramLogExtensions.cs new file mode 100644 index 0000000000..8cc257c12c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/HistogramLogExtensions.cs @@ -0,0 +1,42 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.IO; +using System.Linq; +using HdrHistogram.Utilities; + +namespace HdrHistogram.UnitTests.Persistence +{ + internal static class HistogramLogExtensions + { + public static HistogramBase[] ReadHistograms(this byte[] data) + { + HistogramBase[] actualHistograms; + using (var readerStream = new MemoryStream(data)) + { + actualHistograms = HistogramLogReader.Read(readerStream).ToArray(); + } + return actualHistograms; + } + + public static byte[] WriteLog(this HistogramBase histogram) + { + var startTimeWritten = histogram.StartTimeStamp.ToDateFromMillisecondsSinceEpoch(); + byte[] data; + using (var writerStream = new MemoryStream()) + { + HistogramLogWriter.Write(writerStream, startTimeWritten, histogram); + data = writerStream.ToArray(); + } + return data; + } + public static void SetTimes(this HistogramBase histogram) + { + var startTimeWritten = DateTime.Now; + var endTimeWritten = startTimeWritten.AddMinutes(30); + histogram.StartTimeStamp = startTimeWritten.MillisecondsSinceUnixEpoch(); + histogram.EndTimeStamp = endTimeWritten.MillisecondsSinceUnixEpoch(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/HistogramLogReaderWriterTestBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/HistogramLogReaderWriterTestBase.cs new file mode 100644 index 0000000000..1b5ab2a795 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/HistogramLogReaderWriterTestBase.cs @@ -0,0 +1,302 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using HdrHistogram.Utilities; +using Xunit; + +namespace HdrHistogram.UnitTests.Persistence +{ + public abstract class HistogramLogReaderWriterTestBase + { + private const long DefaultHighestTrackableValue = long.MaxValue - 1; + private const int DefaultSignificantDigits = 3; + //Used as the HighestTrackableValue when reading out test files that were generated from the original Java implementation. + private const long OneHourOfNanoseconds = 3600L * 1000 * 1000 * 1000; + + [Fact] + public void CanReadEmptyLog() + { + byte[] data; + var startTimeWritten = DateTime.Now; + var expectedStartTime = startTimeWritten.SecondsSinceUnixEpoch() + .Round(3); + + using (var writerStream = new MemoryStream()) + { + HistogramLogWriter.Write(writerStream, startTimeWritten); + data = writerStream.ToArray(); + } + + using (var readerStream = new MemoryStream(data)) + { + var reader = new HistogramLogReader(readerStream); + Assert.Empty(reader.ReadHistograms().ToList()); + var actualStartTime = reader.GetStartTime().SecondsSinceUnixEpoch().Round(3); + Assert.Equal(expectedStartTime, actualStartTime); + } + } + + [Theory] + [InlineData(3600L * 1000 * 1000, DefaultSignificantDigits, 1000)] + [InlineData(long.MaxValue / 2, DefaultSignificantDigits, 1000)] + public void CanRoundTripSingleHistogram(long highestTrackableValue, int significantDigits, int multiplier) + { + var histogram = CreatePopulatedHistogram(highestTrackableValue, significantDigits, multiplier); + + histogram.SetTimes(); + var data = histogram.WriteLog(); + var actualHistograms = data.ReadHistograms(); + + Assert.Equal(1, actualHistograms.Length); + HistogramAssert.AreValueEqual(histogram, actualHistograms.Single()); + } + + protected void RoundTripSingleHistogramsWithFullRangesOfCountsAndValues(long count) + { + var value = 1; + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantDigits); + histogram.RecordValueWithCount(value, count); + + histogram.SetTimes(); + var data = histogram.WriteLog(); + var actualHistograms = data.ReadHistograms(); + + Assert.Equal(1, actualHistograms.Length); + HistogramAssert.AreValueEqual(histogram, actualHistograms.Single()); + } + + [Theory] + [InlineData("ATag")] + [InlineData("AnotherTag")] + public void CanRoundTripSingleHistogramsWithSparseValues(string tag) + { + var histogram = Create(DefaultHighestTrackableValue, DefaultSignificantDigits); + histogram.Tag = tag; + histogram.RecordValue(1); + histogram.RecordValue((long.MaxValue / 2) + 1); + + histogram.SetTimes(); + var data = histogram.WriteLog(); + var actualHistograms = data.ReadHistograms(); + + Assert.Equal(1, actualHistograms.Length); + Assert.Equal(tag, actualHistograms[0].Tag); + HistogramAssert.AreValueEqual(histogram, actualHistograms.Single()); + } + + [Fact] + public void CanAppendHistogram() + { + var histogram1 = Create(DefaultHighestTrackableValue, DefaultSignificantDigits); + histogram1.RecordValue(1); + histogram1.RecordValue((long.MaxValue / 2) + 1); + histogram1.SetTimes(); + var histogram2 = Create(DefaultHighestTrackableValue, DefaultSignificantDigits); + histogram2.RecordValue(2); + histogram2.SetTimes(); + + byte[] data; + using (var writerStream = new MemoryStream()) + using (var log = new HistogramLogWriter(writerStream)) + { + log.Append(histogram1); + log.Append(histogram2); + data = writerStream.ToArray(); + } + var actualHistograms = data.ReadHistograms(); + + Assert.Equal(2, actualHistograms.Length); + HistogramAssert.AreValueEqual(histogram1, actualHistograms.First()); + HistogramAssert.AreValueEqual(histogram2, actualHistograms.Skip(1).First()); + } + + [Theory] + [InlineData("tagged-Log.logV2.hlog")] + public void CanReadV2TaggedLogs(string logPath) + { + var readerStream = GetEmbeddedFileStream(logPath); + var reader = new HistogramLogReader(readerStream); + int histogramCount = 0; + long totalCount = 0; + var accumulatedHistogramWithNoTag = Create(85899345920838, DefaultSignificantDigits); + var accumulatedHistogramWithTagA = Create(85899345920838, DefaultSignificantDigits); + foreach (var histogram in reader.ReadHistograms()) + { + histogramCount++; + Assert.IsAssignableFrom(histogram);// "Expected integer value histograms in log file"); + + totalCount += histogram.TotalCount; + if (string.IsNullOrEmpty(histogram.Tag)) + { + accumulatedHistogramWithNoTag.Add(histogram); + } + else if (histogram.Tag == "A") + { + accumulatedHistogramWithTagA.Add(histogram); + } + } + + Assert.Equal(42, histogramCount); + Assert.Equal(32290, totalCount); + + HistogramAssert.AreValueEqual(accumulatedHistogramWithNoTag, accumulatedHistogramWithTagA); + } + + + [Theory] + [InlineData("jHiccup-2.0.7S.logV2.hlog")] + public void CanReadv2Logs(string logPath) + { + var readerStream = GetEmbeddedFileStream(logPath); + var reader = new HistogramLogReader(readerStream); + int histogramCount = 0; + long totalCount = 0; + var accumulatedHistogram = Create(85899345920838, DefaultSignificantDigits); + foreach (var histogram in reader.ReadHistograms()) + { + histogramCount++; + Assert.IsAssignableFrom(histogram);//, "Expected integer value histograms in log file"); + + totalCount += histogram.TotalCount; + accumulatedHistogram.Add(histogram); + } + + Assert.Equal(62, histogramCount); + Assert.Equal(48761, totalCount); + Assert.Equal(1745879039, accumulatedHistogram.GetValueAtPercentile(99.9)); + Assert.Equal(1796210687, accumulatedHistogram.GetMaxValue()); + Assert.Equal(1441812279.474, reader.GetStartTime().SecondsSinceUnixEpoch()); + } + + [Theory] + [InlineData("jHiccup-2.0.1.logV0.hlog", 0, int.MaxValue, 81, 61256, 1510998015, 1569718271, 1438869961.225)] + [InlineData("jHiccup-2.0.1.logV0.hlog", 19, 25, 25, 18492, 459007, 623103, 1438869961.225)] + [InlineData("jHiccup-2.0.1.logV0.hlog", 45, 34, 34, 25439, 1209008127, 1234173951, 1438869961.225)] + public void CanReadv0Logs(string logPath, int skip, int take, + int expectedHistogramCount, int expectedCombinedValueCount, + int expectedCombined999, long expectedCombinedMaxLength, + double expectedStartTime) + { + var readerStream = GetEmbeddedFileStream(logPath); + var reader = new HistogramLogReader(readerStream); + + int histogramCount = 0; + long totalCount = 0; + var accumulatedHistogram = Create(OneHourOfNanoseconds, DefaultSignificantDigits); + var histograms = ((IHistogramLogV1Reader)reader).ReadHistograms() + .Skip(skip) + .Take(take); + foreach (var histogram in histograms) + { + histogramCount++; + totalCount += histogram.TotalCount; + accumulatedHistogram.Add(histogram); + } + Assert.Equal(expectedHistogramCount, histogramCount); + Assert.Equal(expectedCombinedValueCount, totalCount); + Assert.Equal(expectedCombined999, accumulatedHistogram.GetValueAtPercentile(99.9)); + Assert.Equal(expectedCombinedMaxLength, accumulatedHistogram.GetMaxValue()); + Assert.Equal(expectedStartTime, reader.GetStartTime().SecondsSinceUnixEpoch()); + } + + [Theory] + [InlineData("jHiccup-2.0.6.logV1.hlog", 0, int.MaxValue, 88, 65964, 1829765119, 1888485375, 1438867590.285)] + [InlineData("jHiccup-2.0.6.logV1.hlog", 5, 15, 15, 11213, 1019740159, 1032323071, 1438867590.285)] + [InlineData("jHiccup-2.0.6.logV1.hlog", 50, 29, 29, 22630, 1871708159, 1888485375, 1438867590.285)] + [InlineData("ycsb.logV1.hlog", 0, int.MaxValue, 602, 300056, 1214463, 1546239, 1438613579.295)] + [InlineData("ycsb.logV1.hlog", 0, 180, 180, 89893, 1375231, 1546239, 1438613579.295)] + [InlineData("ycsb.logV1.hlog", 180, 520, 422, 210163, 530, 17775, 1438613579.295)] + public void CanReadv1Logs(string logPath, int skip, int take, + int expectedHistogramCount, int expectedCombinedValueCount, + int expectedCombined999, long expectedCombinedMaxLength, + double expectedStartTime) + { + var readerStream = GetEmbeddedFileStream(logPath); + var reader = new HistogramLogReader(readerStream); + int histogramCount = 0; + long totalCount = 0; + + HistogramBase accumulatedHistogram = Create(OneHourOfNanoseconds, DefaultSignificantDigits); + var histograms = reader.ReadHistograms() + .Skip(skip) + .Take(take); + foreach (var histogram in histograms) + { + histogramCount++; + totalCount += histogram.TotalCount; + accumulatedHistogram.Add(histogram); + } + + Assert.Equal(expectedHistogramCount, histogramCount); + Assert.Equal(expectedCombinedValueCount, totalCount); + Assert.Equal(expectedCombined999, accumulatedHistogram.GetValueAtPercentile(99.9)); + Assert.Equal(expectedCombinedMaxLength, accumulatedHistogram.GetMaxValue()); + Assert.Equal(expectedStartTime, reader.GetStartTime().SecondsSinceUnixEpoch()); + } + + [Theory] + [InlineData("ycsb.logV1.hlog", 0, 180, 180, 90033, 1375231, 1546239, 1438613579.295)] + [InlineData("ycsb.logV1.hlog", 180, 520, 421, 209686, 530, 17775, 1438613579.295)] + public void CanReadv1Logs_Skip_PreStart(string logPath, int skip, int take, + int expectedHistogramCount, int expectedCombinedValueCount, + int expectedCombined999, long expectedCombinedMaxLength, + double expectedStartTime) + { + var readerStream = GetEmbeddedFileStream(logPath); + var reader = new HistogramLogReader(readerStream); + int histogramCount = 0; + long totalCount = 0; + + HistogramBase accumulatedHistogram = Create(OneHourOfNanoseconds, DefaultSignificantDigits); + var histograms = reader.ReadHistograms() + .Where(h => h.StartTimeStamp >= reader.GetStartTime().MillisecondsSinceUnixEpoch()) + .Skip(skip) + .Take(take); + foreach (var histogram in histograms) + { + histogramCount++; + totalCount += histogram.TotalCount; + accumulatedHistogram.Add(histogram); + } + + Assert.Equal(expectedHistogramCount, histogramCount); + Assert.Equal(expectedCombinedValueCount, totalCount); + Assert.Equal(expectedCombined999, accumulatedHistogram.GetValueAtPercentile(99.9)); + Assert.Equal(expectedCombinedMaxLength, accumulatedHistogram.GetMaxValue()); + Assert.Equal(expectedStartTime, reader.GetStartTime().SecondsSinceUnixEpoch()); + } + + private HistogramBase CreatePopulatedHistogram(long highestTrackableValue, int significantDigits, int multiplier) + { + var histogram = Create(highestTrackableValue, significantDigits); + //Ensure reasonable number of counts + long i; + for (i = 0; i < 10000; i++) + { + histogram.RecordValue(i * multiplier); + } + //Ensure values recorded across the full range of buckets. + i = 1; + do + { + histogram.RecordValue(i); + } while ((i <<= 1) < highestTrackableValue && i > 0); + return histogram; + } + + private Stream GetEmbeddedFileStream(string filename) + { + var fileName = string.Format(CultureInfo.InvariantCulture, "Microsoft.Azure.Cosmos.Tests.OSS.HdrHistogram.Resources.{0}", filename); + return GetType().GetTypeInfo() + .Assembly + .GetManifestResourceStream(fileName); + } + + internal abstract HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits); + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/IntConcurrentHistogramLogReaderWriterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/IntConcurrentHistogramLogReaderWriterTests.cs new file mode 100644 index 0000000000..d6259f9131 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/IntConcurrentHistogramLogReaderWriterTests.cs @@ -0,0 +1,30 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace HdrHistogram.UnitTests.Persistence +{ + public sealed class IntConcurrentHistogramLogReaderWriterTests : HistogramLogReaderWriterTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new IntConcurrentHistogram(1, highestTrackableValue, numberOfSignificantValueDigits); + } + + [Theory] + [MemberData(nameof(PowersOfTwo))] + public void CanRoundTripSingleHistogramsWithFullRangesOfCountsAndValues(long count) + { + RoundTripSingleHistogramsWithFullRangesOfCountsAndValues(count); + } + + public static IEnumerable PowersOfTwo() + { + return TestCaseGenerator.PowersOfTwo(31) + .Select(v=>new object[1]{v}); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/IntHistogramLogReaderWriterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/IntHistogramLogReaderWriterTests.cs new file mode 100644 index 0000000000..63e712eecc --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/IntHistogramLogReaderWriterTests.cs @@ -0,0 +1,30 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace HdrHistogram.UnitTests.Persistence +{ + + public sealed class IntHistogramLogReaderWriterTests : HistogramLogReaderWriterTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new IntHistogram(highestTrackableValue, numberOfSignificantValueDigits); + } + [Theory] + [MemberData(nameof(PowersOfTwo))] + public void CanRoundTripSingleHistogramsWithFullRangesOfCountsAndValues(long count) + { + RoundTripSingleHistogramsWithFullRangesOfCountsAndValues(count); + } + + public static IEnumerable PowersOfTwo() + { + return TestCaseGenerator.PowersOfTwo(31) + .Select(v => new object[1] { v }); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/LongConcurrentHistogramLogReaderWriterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/LongConcurrentHistogramLogReaderWriterTests.cs new file mode 100644 index 0000000000..6bb56ffb2b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/LongConcurrentHistogramLogReaderWriterTests.cs @@ -0,0 +1,31 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace HdrHistogram.UnitTests.Persistence +{ + + public sealed class LongConcurrentHistogramLogReaderWriterTests : HistogramLogReaderWriterTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new LongConcurrentHistogram(1, highestTrackableValue, numberOfSignificantValueDigits); + } + + [Theory] + [MemberData(nameof(PowersOfTwo))] + public void CanRoundTripSingleHistogramsWithFullRangesOfCountsAndValues(long count) + { + RoundTripSingleHistogramsWithFullRangesOfCountsAndValues(count); + } + + public static IEnumerable PowersOfTwo() + { + return TestCaseGenerator.PowersOfTwo(63) + .Select(v => new object[1] { v }); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/LongHistogramLogReaderWriterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/LongHistogramLogReaderWriterTests.cs new file mode 100644 index 0000000000..efea68bae8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/LongHistogramLogReaderWriterTests.cs @@ -0,0 +1,32 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace HdrHistogram.UnitTests.Persistence +{ + + public sealed class LongHistogramLogReaderWriterTests : HistogramLogReaderWriterTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new LongHistogram(highestTrackableValue, numberOfSignificantValueDigits); + } + + + [Theory] + [MemberData(nameof(PowersOfTwo))] + public void CanRoundTripSingleHistogramsWithFullRangesOfCountsAndValues(long count) + { + RoundTripSingleHistogramsWithFullRangesOfCountsAndValues(count); + } + + public static IEnumerable PowersOfTwo() + { + return TestCaseGenerator.PowersOfTwo(63) + .Select(v => new object[1] { v }); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/ShortHistogramLogReaderWriterTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/ShortHistogramLogReaderWriterTests.cs new file mode 100644 index 0000000000..846b5f08eb --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/ShortHistogramLogReaderWriterTests.cs @@ -0,0 +1,31 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace HdrHistogram.UnitTests.Persistence +{ + + public sealed class ShortHistogramLogReaderWriterTests : HistogramLogReaderWriterTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + return new ShortHistogram(highestTrackableValue, numberOfSignificantValueDigits); + } + + [Theory] + [MemberData(nameof(PowersOfTwo))] + public void CanRoundTripSingleHistogramsWithFullRangesOfCountsAndValues(long count) + { + RoundTripSingleHistogramsWithFullRangesOfCountsAndValues(count); + } + + public static IEnumerable PowersOfTwo() + { + return TestCaseGenerator.PowersOfTwo(15) + .Select(v => new object[1] { v }); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/TestCaseGenerator.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/TestCaseGenerator.cs new file mode 100644 index 0000000000..581de7ede9 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Persistence/TestCaseGenerator.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace HdrHistogram.UnitTests.Persistence +{ + public static class TestCaseGenerator + { + public static IEnumerable PowersOfTwo(int maxBits) + { + for (int i = 0; i < maxBits; i++) + { + yield return (1L << i); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithIntConcurrentHistogram.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithIntConcurrentHistogram.cs new file mode 100644 index 0000000000..843a030edd --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithIntConcurrentHistogram.cs @@ -0,0 +1,33 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests.Recording +{ + + internal sealed class RecorderTestWithIntConcurrentHistogram : RecorderTestsBase + { + internal override HistogramBase CreateHistogram(long id, long min, long max, int sf) + { + //return new IntConcurrentHistogram(id, min, max, sf); + return HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .Create(); + } + + internal override Recorder Create(long min, long max, int sf) + { + return HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .WithThreadSafeReads() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithIntHistogram.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithIntHistogram.cs new file mode 100644 index 0000000000..2cad6b359c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithIntHistogram.cs @@ -0,0 +1,31 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests.Recording +{ + + internal sealed class RecorderTestWithIntHistogram : RecorderTestsBase + { + internal override HistogramBase CreateHistogram(long id, long min, long max, int sf) + { + //return new IntHistogram(id, min, max, sf); + return HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .Create(); + } + + internal override Recorder Create(long min, long max, int sf) + { + return HistogramFactory.With32BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeReads() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithLongConcurrentHistogram.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithLongConcurrentHistogram.cs new file mode 100644 index 0000000000..883c93f10c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithLongConcurrentHistogram.cs @@ -0,0 +1,32 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests.Recording +{ + + internal sealed class RecorderTestWithLongConcurrentHistogram : RecorderTestsBase + { + internal override HistogramBase CreateHistogram(long id, long min, long max, int sf) + { + return HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .Create(); + } + + internal override Recorder Create(long min, long max, int sf) + { + return HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeWrites() + .WithThreadSafeReads() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithLongHistogram.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithLongHistogram.cs new file mode 100644 index 0000000000..7d548f517e --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithLongHistogram.cs @@ -0,0 +1,31 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests.Recording +{ + + internal sealed class RecorderTestWithLongHistogram : RecorderTestsBase + { + internal override HistogramBase CreateHistogram(long id, long min, long max, int sf) + { + //return new LongHistogram(id, min, max, sf); + return HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .Create(); + } + + internal override Recorder Create(long min, long max, int sf) + { + return HistogramFactory.With64BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeReads() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithShortHistogram.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithShortHistogram.cs new file mode 100644 index 0000000000..61726a481a --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestWithShortHistogram.cs @@ -0,0 +1,31 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests.Recording +{ + + internal sealed class RecorderTestWithShortHistogram : RecorderTestsBase + { + internal override HistogramBase CreateHistogram(long id, long min, long max, int sf) + { + //return new ShortHistogram(id, min, max, sf); + return HistogramFactory.With16BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .Create(); + } + + internal override Recorder Create(long min, long max, int sf) + { + return HistogramFactory.With16BitBucketSize() + .WithValuesFrom(min) + .WithValuesUpTo(max) + .WithPrecisionOf(sf) + .WithThreadSafeReads() + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestsBase.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestsBase.cs new file mode 100644 index 0000000000..d848d40e11 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Recording/RecorderTestsBase.cs @@ -0,0 +1,276 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using System; +using System.Threading.Tasks; +using HdrHistogram.Utilities; +using Xunit; + +namespace HdrHistogram.UnitTests.Recording +{ + public abstract class RecorderTestsBase + { + private const long DefautltLowestDiscernibleValue = 1; + private const long DefaultHighestTrackableValue = 7716549600;//TimeStamp.Hours(1); // e.g. for 1 hr in system clock ticks (StopWatch.Frequency) + private const int DefaultSignificantFigures = 3; + + internal abstract HistogramBase CreateHistogram(long id, long min, long max, int sf); + internal abstract Recorder Create(long min, long max, int sf); + + [Theory] + [InlineData(0, 1, DefaultSignificantFigures, "lowestTrackableValue", "lowestTrackableValue must be >= 1")] + [InlineData(1, 1, DefaultSignificantFigures, "highestTrackableValue", "highestTrackableValue must be >= 2 * lowestTrackableValue")] + [InlineData(1, DefaultHighestTrackableValue, 6, "numberOfSignificantValueDigits", "numberOfSignificantValueDigits must be between 0 and 5")] + [InlineData(1, DefaultHighestTrackableValue, -1, "numberOfSignificantValueDigits", "numberOfSignificantValueDigits must be between 0 and 5")] + public void ConstructorShouldRejectInvalidParameters( + long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits, + string errorParamName, string errorMessage) + { + var ex = Assert.Throws(() => + Create(lowestTrackableValue, + highestTrackableValue, + numberOfSignificantValueDigits)); + Assert.Equal(errorParamName, ex.ParamName); + Assert.StartsWith(errorMessage, ex.Message); + } + + [Fact] + public void GetIntervalHistogram_returns_alternating_instances_from_factory() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + var a = recorder.GetIntervalHistogram(); + var b = recorder.GetIntervalHistogram(a); + var c = recorder.GetIntervalHistogram(b); + var d = recorder.GetIntervalHistogram(c); + + Assert.NotSame(a, b); + Assert.Same(a, c); + Assert.NotSame(a, d); + Assert.Same(b, d); + } + + [Fact] + public void GetIntervalHistogram_returns_current_histogram_values() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1); + recorder.RecordValue(10); + recorder.RecordValue(100); + var histogram = recorder.GetIntervalHistogram(); + Assert.Equal(1, histogram.GetCountAtValue(1)); + Assert.Equal(1, histogram.GetCountAtValue(10)); + Assert.Equal(1, histogram.GetCountAtValue(100)); + } + + [Fact] + public void GetIntervalHistogram_causes_recording_to_happen_on_new_histogram() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1); + var histogramPrimary = recorder.GetIntervalHistogram(); + Assert.Equal(1, histogramPrimary.GetCountAtValue(1)); + + recorder.RecordValue(10); + recorder.RecordValue(100); + var histogramSecondary = recorder.GetIntervalHistogram(histogramPrimary); + + Assert.Equal(0, histogramSecondary.GetCountAtValue(1)); + Assert.Equal(1, histogramSecondary.GetCountAtValue(10)); + Assert.Equal(1, histogramSecondary.GetCountAtValue(100)); + } + + [Fact] + public void GetIntervalHistogram_resets_recycled_histogram() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1); + recorder.RecordValue(10); + recorder.RecordValue(100); + var histogramPrimary = recorder.GetIntervalHistogram(); + + recorder.RecordValue(1); + recorder.RecordValue(10); + recorder.RecordValue(100); + var histogramSecondary = recorder.GetIntervalHistogram(histogramPrimary); + + Assert.Equal(0, histogramPrimary.GetCountAtValue(1)); + Assert.Equal(0, histogramPrimary.GetCountAtValue(10)); + Assert.Equal(0, histogramPrimary.GetCountAtValue(100)); + Assert.Equal(1, histogramSecondary.GetCountAtValue(1)); + Assert.Equal(1, histogramSecondary.GetCountAtValue(10)); + Assert.Equal(1, histogramSecondary.GetCountAtValue(100)); + } + + [Fact] + public void RecordValue_increments_TotalCount() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1000); + var histogram = recorder.GetIntervalHistogram(); + Assert.Equal(1, histogram.TotalCount); + } + + [Fact] + public void RecordValue_increments_CountAtValue() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1000); + recorder.RecordValue(1000); + recorder.RecordValue(1000); + var histogram = recorder.GetIntervalHistogram(); + Assert.Equal(3, histogram.GetCountAtValue(1000)); + } + + [Fact] + public void RecordValue_Overflow_ShouldThrowException() + { + var highestTrackableValue = DefaultHighestTrackableValue; + var recorder = Create(DefautltLowestDiscernibleValue, highestTrackableValue, DefaultSignificantFigures); + Assert.Throws(() => recorder.RecordValue(highestTrackableValue * 3)); + } + + [Fact] + public void RecordValueWithCount_increments_TotalCount() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValueWithCount(1000, 10); + var histogram = recorder.GetIntervalHistogram(); + Assert.Equal(10, histogram.TotalCount); + } + + [Fact] + public void RecordValueWithCount_increments_CountAtValue() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValueWithCount(1000, 10); + recorder.RecordValueWithCount(1000, 10); + recorder.RecordValueWithCount(5000, 20); + var histogram = recorder.GetIntervalHistogram(); + Assert.Equal(20, histogram.GetCountAtValue(1000)); + Assert.Equal(20, histogram.GetCountAtValue(5000)); + } + + [Fact] + public void RecordValueWithCount_Overflow_ShouldThrowException() + { + var highestTrackableValue = DefaultHighestTrackableValue; + var recorder = Create(DefautltLowestDiscernibleValue, highestTrackableValue, DefaultSignificantFigures); + Assert.Throws(() => recorder.RecordValueWithCount(highestTrackableValue * 3, 100)); + } + + [Fact] + public void RecordValueWithExpectedInterval() + { + var TestValueLevel = 4L; + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + var valueHistogram = new LongHistogram(DefaultHighestTrackableValue, DefaultSignificantFigures); + + recorder.RecordValueWithExpectedInterval(TestValueLevel, TestValueLevel / 4); + valueHistogram.RecordValue(TestValueLevel); + + var intervalHistogram = recorder.GetIntervalHistogram(); + // The data will include corrected samples: + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 1) / 4)); + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 2) / 4)); + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 3) / 4)); + Assert.Equal(1L, intervalHistogram.GetCountAtValue((TestValueLevel * 4) / 4)); + Assert.Equal(4L, intervalHistogram.TotalCount); + // But the raw data will not: + Assert.Equal(0L, valueHistogram.GetCountAtValue((TestValueLevel * 1) / 4)); + Assert.Equal(0L, valueHistogram.GetCountAtValue((TestValueLevel * 2) / 4)); + Assert.Equal(0L, valueHistogram.GetCountAtValue((TestValueLevel * 3) / 4)); + Assert.Equal(1L, valueHistogram.GetCountAtValue((TestValueLevel * 4) / 4)); + Assert.Equal(1L, valueHistogram.TotalCount); + } + + [Fact] + public void RecordAction_increments_TotalCount() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + + recorder.Record(() => { }); + + var longHistogram = recorder.GetIntervalHistogram(); + Assert.Equal(1, longHistogram.TotalCount); + } + + [Fact] + public void Reset_clears_counts_for_instances() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1); + recorder.RecordValue(10); + recorder.RecordValue(100); + var histogramPrimary = recorder.GetIntervalHistogram(); + + recorder.RecordValue(1); + recorder.RecordValue(10); + recorder.RecordValue(100); + + recorder.Reset(); + var histogramSecondary = recorder.GetIntervalHistogram(histogramPrimary); + + + Assert.Equal(0, histogramPrimary.TotalCount); + Assert.Equal(0, histogramSecondary.TotalCount); + } + + [Fact] + public void GetIntervalHistogramInto_copies_data_over_provided_Histogram() + { + var originalStart = DateTime.Today.AddDays(-1).MillisecondsSinceUnixEpoch(); + var originalEnd = DateTime.Today.MillisecondsSinceUnixEpoch(); + var targetHistogram = new LongHistogram(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + targetHistogram.StartTimeStamp = originalStart; + targetHistogram.RecordValue(1); + targetHistogram.RecordValue(10); + targetHistogram.RecordValue(100); + targetHistogram.EndTimeStamp = originalEnd; + + + Assert.Equal(3, targetHistogram.TotalCount); + Assert.Equal(1, targetHistogram.GetCountAtValue(1)); + Assert.Equal(1, targetHistogram.GetCountAtValue(10)); + Assert.Equal(1, targetHistogram.GetCountAtValue(100)); + + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1000); + recorder.RecordValue(10000); + recorder.RecordValue(100000); + + recorder.GetIntervalHistogramInto(targetHistogram); + + Assert.Equal(3, targetHistogram.TotalCount); + Assert.Equal(0, targetHistogram.GetCountAtValue(1)); + Assert.Equal(0, targetHistogram.GetCountAtValue(10)); + Assert.Equal(0, targetHistogram.GetCountAtValue(100)); + Assert.Equal(1, targetHistogram.GetCountAtValue(1000)); + Assert.Equal(1, targetHistogram.GetCountAtValue(10000)); + Assert.Equal(1, targetHistogram.GetCountAtValue(100000)); + Assert.NotEqual(originalStart, targetHistogram.StartTimeStamp); + Assert.NotEqual(originalEnd, targetHistogram.EndTimeStamp); + } + + [Fact] + public void Using_external_histogram_for_recycling_throws() + { + const int id = -1000; + var externallyCreatedHistogram = CreateHistogram(id, DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + var recorder = Create(1, DefaultHighestTrackableValue, DefaultSignificantFigures); + recorder.RecordValue(1000); + + Assert.Throws(() => recorder.GetIntervalHistogram(externallyCreatedHistogram)); + + recorder.GetIntervalHistogramInto(externallyCreatedHistogram); + } + + [Fact] + public void RecordScope_increments_TotalCount() + { + var recorder = Create(DefautltLowestDiscernibleValue, DefaultHighestTrackableValue, DefaultSignificantFigures); + using (recorder.RecordScope()) { } + var histogram = recorder.GetIntervalHistogram(); + Assert.Equal(1, histogram.TotalCount); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/IsLastValueBug.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/IsLastValueBug.hgrm new file mode 100644 index 0000000000..9121038cc2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/IsLastValueBug.hgrm @@ -0,0 +1,124 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 1.000 0.000000000000 7604459 1.00 + 1.000 0.100000000000 7604459 1.11 + 1.000 0.200000000000 7604459 1.25 + 1.000 0.300000000000 7604459 1.43 + 1.000 0.400000000000 7604459 1.67 + 1.000 0.500000000000 7604459 2.00 + 1.000 0.550000000000 7604459 2.22 + 1.000 0.600000000000 7604459 2.50 + 1.000 0.650000000000 7604459 2.86 + 1.000 0.700000000000 7604459 3.33 + 1.000 0.750000000000 7604459 4.00 + 383.000 0.775000000000 9999983 4.44 + 383.000 0.800000000000 9999983 5.00 + 383.000 0.825000000000 9999983 5.71 + 383.000 0.850000000000 9999983 6.67 + 383.000 0.875000000000 9999983 8.00 + 383.000 0.887500000000 9999983 8.89 + 383.000 0.900000000000 9999983 10.00 + 383.000 0.912500000000 9999983 11.43 + 383.000 0.925000000000 9999983 13.33 + 383.000 0.937500000000 9999983 16.00 + 383.000 0.943750000000 9999983 17.78 + 383.000 0.950000000000 9999983 20.00 + 383.000 0.956250000000 9999983 22.86 + 383.000 0.962500000000 9999983 26.67 + 383.000 0.968750000000 9999983 32.00 + 383.000 0.971875000000 9999983 35.56 + 383.000 0.975000000000 9999983 40.00 + 383.000 0.978125000000 9999983 45.71 + 383.000 0.981250000000 9999983 53.33 + 383.000 0.984375000000 9999983 64.00 + 383.000 0.985937500000 9999983 71.11 + 383.000 0.987500000000 9999983 80.00 + 383.000 0.989062500000 9999983 91.43 + 383.000 0.990625000000 9999983 106.67 + 383.000 0.992187500000 9999983 128.00 + 383.000 0.992968750000 9999983 142.22 + 383.000 0.993750000000 9999983 160.00 + 383.000 0.994531250000 9999983 182.86 + 383.000 0.995312500000 9999983 213.33 + 383.000 0.996093750000 9999983 256.00 + 383.000 0.996484375000 9999983 284.44 + 383.000 0.996875000000 9999983 320.00 + 383.000 0.997265625000 9999983 365.71 + 383.000 0.997656250000 9999983 426.67 + 383.000 0.998046875000 9999983 512.00 + 383.000 0.998242187500 9999983 568.89 + 383.000 0.998437500000 9999983 640.00 + 383.000 0.998632812500 9999983 731.43 + 383.000 0.998828125000 9999983 853.33 + 383.000 0.999023437500 9999983 1024.00 + 383.000 0.999121093750 9999983 1137.78 + 383.000 0.999218750000 9999983 1280.00 + 383.000 0.999316406250 9999983 1462.86 + 383.000 0.999414062500 9999983 1706.67 + 383.000 0.999511718750 9999983 2048.00 + 383.000 0.999560546875 9999983 2275.56 + 383.000 0.999609375000 9999983 2560.00 + 383.000 0.999658203125 9999983 2925.71 + 383.000 0.999707031250 9999983 3413.33 + 383.000 0.999755859375 9999983 4096.00 + 383.000 0.999780273438 9999983 4551.11 + 383.000 0.999804687500 9999983 5120.00 + 383.000 0.999829101563 9999983 5851.43 + 383.000 0.999853515625 9999983 6826.67 + 383.000 0.999877929688 9999983 8192.00 + 383.000 0.999890136719 9999983 9102.22 + 383.000 0.999902343750 9999983 10240.00 + 383.000 0.999914550781 9999983 11702.86 + 383.000 0.999926757813 9999983 13653.33 + 383.000 0.999938964844 9999983 16384.00 + 383.000 0.999945068359 9999983 18204.44 + 383.000 0.999951171875 9999983 20480.00 + 383.000 0.999957275391 9999983 23405.71 + 383.000 0.999963378906 9999983 27306.67 + 383.000 0.999969482422 9999983 32768.00 + 383.000 0.999972534180 9999983 36408.89 + 383.000 0.999975585938 9999983 40960.00 + 383.000 0.999978637695 9999983 46811.43 + 383.000 0.999981689453 9999983 54613.33 + 383.000 0.999984741211 9999983 65536.00 + 383.000 0.999986267090 9999983 72817.78 + 383.000 0.999987792969 9999983 81920.00 + 383.000 0.999989318848 9999983 93622.86 + 383.000 0.999990844727 9999983 109226.67 + 383.000 0.999992370605 9999983 131072.00 + 383.000 0.999993133545 9999983 145635.56 + 383.000 0.999993896484 9999983 163840.00 + 383.000 0.999994659424 9999983 187245.71 + 383.000 0.999995422363 9999983 218453.33 + 383.000 0.999996185303 9999983 262144.00 + 383.000 0.999996566772 9999983 291271.11 + 383.000 0.999996948242 9999983 327680.00 + 383.000 0.999997329712 9999983 374491.43 + 383.000 0.999997711182 9999983 436906.67 + 383.000 0.999998092651 9999983 524288.00 + 383.000 0.999998283386 9999983 582542.22 + 453.000 0.999998474121 9999985 655360.00 + 511.000 0.999998664856 9999987 748982.86 + 537.000 0.999998855591 9999990 873813.33 + 672.000 0.999999046326 9999991 1048576.00 + 777.000 0.999999141693 9999992 1165084.44 + 18143.000 0.999999237061 9999993 1310720.00 + 208127.000 0.999999332428 9999994 1497965.71 + 224639.000 0.999999427795 9999995 1747626.67 + 229759.000 0.999999523163 9999996 2097152.00 + 229759.000 0.999999570847 9999996 2330168.89 + 230271.000 0.999999618530 9999997 2621440.00 + 230271.000 0.999999666214 9999997 2995931.43 + 258943.000 0.999999713898 9999998 3495253.33 + 258943.000 0.999999761581 9999998 4194304.00 + 258943.000 0.999999785423 9999998 4660337.78 + 275711.000 0.999999809265 9999999 5242880.00 + 275711.000 0.999999833107 9999999 5991862.86 + 275711.000 0.999999856949 9999999 6990506.67 + 275711.000 0.999999880791 9999999 8388608.00 + 275711.000 0.999999892712 9999999 9320675.55 + 282111.000 0.999999904633 10000000 10485760.00 + 282111.000 1.000000000000 10000000 +#[Mean = 92.682, StdDeviation = 262.259] +#[Max = 282111.000, Total count = 10000000] +#[Buckets = 26, SubBuckets = 2048] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_10percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_10percPerHalfDistance.csv new file mode 100644 index 0000000000..a1434d3ef9 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_10percPerHalfDistance.csv @@ -0,0 +1,38 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.0,0.000000000000,1,1.00 +0.0,0.050000000000,1,1.05 +0.0,0.100000000000,1,1.11 +0.1,0.150000000000,2,1.18 +0.1,0.200000000000,2,1.25 +0.2,0.250000000000,3,1.33 +0.2,0.300000000000,3,1.43 +0.3,0.350000000000,4,1.54 +0.3,0.400000000000,4,1.67 +0.4,0.450000000000,5,1.82 +0.4,0.500000000000,5,2.00 +0.5,0.525000000000,6,2.11 +0.5,0.550000000000,6,2.22 +0.5,0.575000000000,6,2.35 +0.5,0.600000000000,6,2.50 +0.6,0.625000000000,7,2.67 +0.6,0.650000000000,7,2.86 +0.6,0.675000000000,7,3.08 +0.6,0.700000000000,7,3.33 +0.7,0.725000000000,8,3.64 +0.7,0.750000000000,8,4.00 +0.7,0.762500000000,8,4.21 +0.7,0.775000000000,8,4.44 +0.7,0.787500000000,8,4.71 +0.7,0.800000000000,8,5.00 +0.8,0.812500000000,9,5.33 +0.8,0.825000000000,9,5.71 +0.8,0.837500000000,9,6.15 +0.8,0.850000000000,9,6.67 +0.8,0.862500000000,9,7.27 +0.8,0.875000000000,9,8.00 +0.8,0.881250000000,9,8.42 +0.8,0.887500000000,9,8.89 +0.8,0.893750000000,9,9.41 +0.8,0.900000000000,9,10.00 +0.9,0.906250000000,10,10.67 +0.9,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm new file mode 100644 index 0000000000..59d5d41b2a --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm @@ -0,0 +1,42 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.0 0.000000000000 1 1.00 + 0.0 0.050000000000 1 1.05 + 0.0 0.100000000000 1 1.11 + 0.1 0.150000000000 2 1.18 + 0.1 0.200000000000 2 1.25 + 0.2 0.250000000000 3 1.33 + 0.2 0.300000000000 3 1.43 + 0.3 0.350000000000 4 1.54 + 0.3 0.400000000000 4 1.67 + 0.4 0.450000000000 5 1.82 + 0.4 0.500000000000 5 2.00 + 0.5 0.525000000000 6 2.11 + 0.5 0.550000000000 6 2.22 + 0.5 0.575000000000 6 2.35 + 0.5 0.600000000000 6 2.50 + 0.6 0.625000000000 7 2.67 + 0.6 0.650000000000 7 2.86 + 0.6 0.675000000000 7 3.08 + 0.6 0.700000000000 7 3.33 + 0.7 0.725000000000 8 3.64 + 0.7 0.750000000000 8 4.00 + 0.7 0.762500000000 8 4.21 + 0.7 0.775000000000 8 4.44 + 0.7 0.787500000000 8 4.71 + 0.7 0.800000000000 8 5.00 + 0.8 0.812500000000 9 5.33 + 0.8 0.825000000000 9 5.71 + 0.8 0.837500000000 9 6.15 + 0.8 0.850000000000 9 6.67 + 0.8 0.862500000000 9 7.27 + 0.8 0.875000000000 9 8.00 + 0.8 0.881250000000 9 8.42 + 0.8 0.887500000000 9 8.89 + 0.8 0.893750000000 9 9.41 + 0.8 0.900000000000 9 10.00 + 0.9 0.906250000000 10 10.67 + 0.9 1.000000000000 10 +#[Mean = 0.5, StdDeviation = 0.3] +#[Max = 0.9, Total count = 10] +#[Buckets = 32, SubBuckets = 32] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_20percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_20percPerHalfDistance.csv new file mode 100644 index 0000000000..324fc7668e --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_20percPerHalfDistance.csv @@ -0,0 +1,72 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.0,0.000000000000,1,1.00 +0.0,0.025000000000,1,1.03 +0.0,0.050000000000,1,1.05 +0.0,0.075000000000,1,1.08 +0.0,0.100000000000,1,1.11 +0.1,0.125000000000,2,1.14 +0.1,0.150000000000,2,1.18 +0.1,0.175000000000,2,1.21 +0.1,0.200000000000,2,1.25 +0.2,0.225000000000,3,1.29 +0.2,0.250000000000,3,1.33 +0.2,0.275000000000,3,1.38 +0.2,0.300000000000,3,1.43 +0.3,0.325000000000,4,1.48 +0.3,0.350000000000,4,1.54 +0.3,0.375000000000,4,1.60 +0.3,0.400000000000,4,1.67 +0.4,0.425000000000,5,1.74 +0.4,0.450000000000,5,1.82 +0.4,0.475000000000,5,1.90 +0.4,0.500000000000,5,2.00 +0.5,0.512500000000,6,2.05 +0.5,0.525000000000,6,2.11 +0.5,0.537500000000,6,2.16 +0.5,0.550000000000,6,2.22 +0.5,0.562500000000,6,2.29 +0.5,0.575000000000,6,2.35 +0.5,0.587500000000,6,2.42 +0.5,0.600000000000,6,2.50 +0.6,0.612500000000,7,2.58 +0.6,0.625000000000,7,2.67 +0.6,0.637500000000,7,2.76 +0.6,0.650000000000,7,2.86 +0.6,0.662500000000,7,2.96 +0.6,0.675000000000,7,3.08 +0.6,0.687500000000,7,3.20 +0.6,0.700000000000,7,3.33 +0.7,0.712500000000,8,3.48 +0.7,0.725000000000,8,3.64 +0.7,0.737500000000,8,3.81 +0.7,0.750000000000,8,4.00 +0.7,0.756250000000,8,4.10 +0.7,0.762500000000,8,4.21 +0.7,0.768750000000,8,4.32 +0.7,0.775000000000,8,4.44 +0.7,0.781250000000,8,4.57 +0.7,0.787500000000,8,4.71 +0.7,0.793750000000,8,4.85 +0.7,0.800000000000,8,5.00 +0.8,0.806250000000,9,5.16 +0.8,0.812500000000,9,5.33 +0.8,0.818750000000,9,5.52 +0.8,0.825000000000,9,5.71 +0.8,0.831250000000,9,5.93 +0.8,0.837500000000,9,6.15 +0.8,0.843750000000,9,6.40 +0.8,0.850000000000,9,6.67 +0.8,0.856250000000,9,6.96 +0.8,0.862500000000,9,7.27 +0.8,0.868750000000,9,7.62 +0.8,0.875000000000,9,8.00 +0.8,0.878125000000,9,8.21 +0.8,0.881250000000,9,8.42 +0.8,0.884375000000,9,8.65 +0.8,0.887500000000,9,8.89 +0.8,0.890625000000,9,9.14 +0.8,0.893750000000,9,9.41 +0.8,0.896875000000,9,9.70 +0.8,0.900000000000,9,10.00 +0.9,0.903125000000,10,10.32 +0.9,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm new file mode 100644 index 0000000000..b20f3902c8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm @@ -0,0 +1,76 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.0 0.000000000000 1 1.00 + 0.0 0.025000000000 1 1.03 + 0.0 0.050000000000 1 1.05 + 0.0 0.075000000000 1 1.08 + 0.0 0.100000000000 1 1.11 + 0.1 0.125000000000 2 1.14 + 0.1 0.150000000000 2 1.18 + 0.1 0.175000000000 2 1.21 + 0.1 0.200000000000 2 1.25 + 0.2 0.225000000000 3 1.29 + 0.2 0.250000000000 3 1.33 + 0.2 0.275000000000 3 1.38 + 0.2 0.300000000000 3 1.43 + 0.3 0.325000000000 4 1.48 + 0.3 0.350000000000 4 1.54 + 0.3 0.375000000000 4 1.60 + 0.3 0.400000000000 4 1.67 + 0.4 0.425000000000 5 1.74 + 0.4 0.450000000000 5 1.82 + 0.4 0.475000000000 5 1.90 + 0.4 0.500000000000 5 2.00 + 0.5 0.512500000000 6 2.05 + 0.5 0.525000000000 6 2.11 + 0.5 0.537500000000 6 2.16 + 0.5 0.550000000000 6 2.22 + 0.5 0.562500000000 6 2.29 + 0.5 0.575000000000 6 2.35 + 0.5 0.587500000000 6 2.42 + 0.5 0.600000000000 6 2.50 + 0.6 0.612500000000 7 2.58 + 0.6 0.625000000000 7 2.67 + 0.6 0.637500000000 7 2.76 + 0.6 0.650000000000 7 2.86 + 0.6 0.662500000000 7 2.96 + 0.6 0.675000000000 7 3.08 + 0.6 0.687500000000 7 3.20 + 0.6 0.700000000000 7 3.33 + 0.7 0.712500000000 8 3.48 + 0.7 0.725000000000 8 3.64 + 0.7 0.737500000000 8 3.81 + 0.7 0.750000000000 8 4.00 + 0.7 0.756250000000 8 4.10 + 0.7 0.762500000000 8 4.21 + 0.7 0.768750000000 8 4.32 + 0.7 0.775000000000 8 4.44 + 0.7 0.781250000000 8 4.57 + 0.7 0.787500000000 8 4.71 + 0.7 0.793750000000 8 4.85 + 0.7 0.800000000000 8 5.00 + 0.8 0.806250000000 9 5.16 + 0.8 0.812500000000 9 5.33 + 0.8 0.818750000000 9 5.52 + 0.8 0.825000000000 9 5.71 + 0.8 0.831250000000 9 5.93 + 0.8 0.837500000000 9 6.15 + 0.8 0.843750000000 9 6.40 + 0.8 0.850000000000 9 6.67 + 0.8 0.856250000000 9 6.96 + 0.8 0.862500000000 9 7.27 + 0.8 0.868750000000 9 7.62 + 0.8 0.875000000000 9 8.00 + 0.8 0.878125000000 9 8.21 + 0.8 0.881250000000 9 8.42 + 0.8 0.884375000000 9 8.65 + 0.8 0.887500000000 9 8.89 + 0.8 0.890625000000 9 9.14 + 0.8 0.893750000000 9 9.41 + 0.8 0.896875000000 9 9.70 + 0.8 0.900000000000 9 10.00 + 0.9 0.903125000000 10 10.32 + 0.9 1.000000000000 10 +#[Mean = 0.5, StdDeviation = 0.3] +#[Max = 0.9, Total count = 10] +#[Buckets = 32, SubBuckets = 32] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_5percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_5percPerHalfDistance.csv new file mode 100644 index 0000000000..e5b89cda79 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_5percPerHalfDistance.csv @@ -0,0 +1,21 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.0,0.000000000000,1,1.00 +0.0,0.100000000000,1,1.11 +0.1,0.200000000000,2,1.25 +0.2,0.300000000000,3,1.43 +0.3,0.400000000000,4,1.67 +0.4,0.500000000000,5,2.00 +0.5,0.550000000000,6,2.22 +0.5,0.600000000000,6,2.50 +0.6,0.650000000000,7,2.86 +0.6,0.700000000000,7,3.33 +0.7,0.750000000000,8,4.00 +0.7,0.775000000000,8,4.44 +0.7,0.800000000000,8,5.00 +0.8,0.825000000000,9,5.71 +0.8,0.850000000000,9,6.67 +0.8,0.875000000000,9,8.00 +0.8,0.887500000000,9,8.89 +0.8,0.900000000000,9,10.00 +0.9,0.912500000000,10,11.43 +0.9,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm new file mode 100644 index 0000000000..73df312b59 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_1sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm @@ -0,0 +1,25 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.0 0.000000000000 1 1.00 + 0.0 0.100000000000 1 1.11 + 0.1 0.200000000000 2 1.25 + 0.2 0.300000000000 3 1.43 + 0.3 0.400000000000 4 1.67 + 0.4 0.500000000000 5 2.00 + 0.5 0.550000000000 6 2.22 + 0.5 0.600000000000 6 2.50 + 0.6 0.650000000000 7 2.86 + 0.6 0.700000000000 7 3.33 + 0.7 0.750000000000 8 4.00 + 0.7 0.775000000000 8 4.44 + 0.7 0.800000000000 8 5.00 + 0.8 0.825000000000 9 5.71 + 0.8 0.850000000000 9 6.67 + 0.8 0.875000000000 9 8.00 + 0.8 0.887500000000 9 8.89 + 0.8 0.900000000000 9 10.00 + 0.9 0.912500000000 10 11.43 + 0.9 1.000000000000 10 +#[Mean = 0.5, StdDeviation = 0.3] +#[Max = 0.9, Total count = 10] +#[Buckets = 32, SubBuckets = 32] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_10percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_10percPerHalfDistance.csv new file mode 100644 index 0000000000..9a24ff4f9d --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_10percPerHalfDistance.csv @@ -0,0 +1,38 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.00,0.000000000000,1,1.00 +0.00,0.050000000000,1,1.05 +0.00,0.100000000000,1,1.11 +0.10,0.150000000000,2,1.18 +0.10,0.200000000000,2,1.25 +0.20,0.250000000000,3,1.33 +0.20,0.300000000000,3,1.43 +0.30,0.350000000000,4,1.54 +0.30,0.400000000000,4,1.67 +0.40,0.450000000000,5,1.82 +0.40,0.500000000000,5,2.00 +0.50,0.525000000000,6,2.11 +0.50,0.550000000000,6,2.22 +0.50,0.575000000000,6,2.35 +0.50,0.600000000000,6,2.50 +0.60,0.625000000000,7,2.67 +0.60,0.650000000000,7,2.86 +0.60,0.675000000000,7,3.08 +0.60,0.700000000000,7,3.33 +0.70,0.725000000000,8,3.64 +0.70,0.750000000000,8,4.00 +0.70,0.762500000000,8,4.21 +0.70,0.775000000000,8,4.44 +0.70,0.787500000000,8,4.71 +0.70,0.800000000000,8,5.00 +0.80,0.812500000000,9,5.33 +0.80,0.825000000000,9,5.71 +0.80,0.837500000000,9,6.15 +0.80,0.850000000000,9,6.67 +0.80,0.862500000000,9,7.27 +0.80,0.875000000000,9,8.00 +0.80,0.881250000000,9,8.42 +0.80,0.887500000000,9,8.89 +0.80,0.893750000000,9,9.41 +0.80,0.900000000000,9,10.00 +0.90,0.906250000000,10,10.67 +0.90,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm new file mode 100644 index 0000000000..7ebe494537 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm @@ -0,0 +1,42 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.00 0.000000000000 1 1.00 + 0.00 0.050000000000 1 1.05 + 0.00 0.100000000000 1 1.11 + 0.10 0.150000000000 2 1.18 + 0.10 0.200000000000 2 1.25 + 0.20 0.250000000000 3 1.33 + 0.20 0.300000000000 3 1.43 + 0.30 0.350000000000 4 1.54 + 0.30 0.400000000000 4 1.67 + 0.40 0.450000000000 5 1.82 + 0.40 0.500000000000 5 2.00 + 0.50 0.525000000000 6 2.11 + 0.50 0.550000000000 6 2.22 + 0.50 0.575000000000 6 2.35 + 0.50 0.600000000000 6 2.50 + 0.60 0.625000000000 7 2.67 + 0.60 0.650000000000 7 2.86 + 0.60 0.675000000000 7 3.08 + 0.60 0.700000000000 7 3.33 + 0.70 0.725000000000 8 3.64 + 0.70 0.750000000000 8 4.00 + 0.70 0.762500000000 8 4.21 + 0.70 0.775000000000 8 4.44 + 0.70 0.787500000000 8 4.71 + 0.70 0.800000000000 8 5.00 + 0.80 0.812500000000 9 5.33 + 0.80 0.825000000000 9 5.71 + 0.80 0.837500000000 9 6.15 + 0.80 0.850000000000 9 6.67 + 0.80 0.862500000000 9 7.27 + 0.80 0.875000000000 9 8.00 + 0.80 0.881250000000 9 8.42 + 0.80 0.887500000000 9 8.89 + 0.80 0.893750000000 9 9.41 + 0.80 0.900000000000 9 10.00 + 0.90 0.906250000000 10 10.67 + 0.90 1.000000000000 10 +#[Mean = 0.45, StdDeviation = 0.29] +#[Max = 0.90, Total count = 10] +#[Buckets = 29, SubBuckets = 256] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_20percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_20percPerHalfDistance.csv new file mode 100644 index 0000000000..9174685d74 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_20percPerHalfDistance.csv @@ -0,0 +1,72 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.00,0.000000000000,1,1.00 +0.00,0.025000000000,1,1.03 +0.00,0.050000000000,1,1.05 +0.00,0.075000000000,1,1.08 +0.00,0.100000000000,1,1.11 +0.10,0.125000000000,2,1.14 +0.10,0.150000000000,2,1.18 +0.10,0.175000000000,2,1.21 +0.10,0.200000000000,2,1.25 +0.20,0.225000000000,3,1.29 +0.20,0.250000000000,3,1.33 +0.20,0.275000000000,3,1.38 +0.20,0.300000000000,3,1.43 +0.30,0.325000000000,4,1.48 +0.30,0.350000000000,4,1.54 +0.30,0.375000000000,4,1.60 +0.30,0.400000000000,4,1.67 +0.40,0.425000000000,5,1.74 +0.40,0.450000000000,5,1.82 +0.40,0.475000000000,5,1.90 +0.40,0.500000000000,5,2.00 +0.50,0.512500000000,6,2.05 +0.50,0.525000000000,6,2.11 +0.50,0.537500000000,6,2.16 +0.50,0.550000000000,6,2.22 +0.50,0.562500000000,6,2.29 +0.50,0.575000000000,6,2.35 +0.50,0.587500000000,6,2.42 +0.50,0.600000000000,6,2.50 +0.60,0.612500000000,7,2.58 +0.60,0.625000000000,7,2.67 +0.60,0.637500000000,7,2.76 +0.60,0.650000000000,7,2.86 +0.60,0.662500000000,7,2.96 +0.60,0.675000000000,7,3.08 +0.60,0.687500000000,7,3.20 +0.60,0.700000000000,7,3.33 +0.70,0.712500000000,8,3.48 +0.70,0.725000000000,8,3.64 +0.70,0.737500000000,8,3.81 +0.70,0.750000000000,8,4.00 +0.70,0.756250000000,8,4.10 +0.70,0.762500000000,8,4.21 +0.70,0.768750000000,8,4.32 +0.70,0.775000000000,8,4.44 +0.70,0.781250000000,8,4.57 +0.70,0.787500000000,8,4.71 +0.70,0.793750000000,8,4.85 +0.70,0.800000000000,8,5.00 +0.80,0.806250000000,9,5.16 +0.80,0.812500000000,9,5.33 +0.80,0.818750000000,9,5.52 +0.80,0.825000000000,9,5.71 +0.80,0.831250000000,9,5.93 +0.80,0.837500000000,9,6.15 +0.80,0.843750000000,9,6.40 +0.80,0.850000000000,9,6.67 +0.80,0.856250000000,9,6.96 +0.80,0.862500000000,9,7.27 +0.80,0.868750000000,9,7.62 +0.80,0.875000000000,9,8.00 +0.80,0.878125000000,9,8.21 +0.80,0.881250000000,9,8.42 +0.80,0.884375000000,9,8.65 +0.80,0.887500000000,9,8.89 +0.80,0.890625000000,9,9.14 +0.80,0.893750000000,9,9.41 +0.80,0.896875000000,9,9.70 +0.80,0.900000000000,9,10.00 +0.90,0.903125000000,10,10.32 +0.90,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm new file mode 100644 index 0000000000..a1a67ff652 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm @@ -0,0 +1,76 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.00 0.000000000000 1 1.00 + 0.00 0.025000000000 1 1.03 + 0.00 0.050000000000 1 1.05 + 0.00 0.075000000000 1 1.08 + 0.00 0.100000000000 1 1.11 + 0.10 0.125000000000 2 1.14 + 0.10 0.150000000000 2 1.18 + 0.10 0.175000000000 2 1.21 + 0.10 0.200000000000 2 1.25 + 0.20 0.225000000000 3 1.29 + 0.20 0.250000000000 3 1.33 + 0.20 0.275000000000 3 1.38 + 0.20 0.300000000000 3 1.43 + 0.30 0.325000000000 4 1.48 + 0.30 0.350000000000 4 1.54 + 0.30 0.375000000000 4 1.60 + 0.30 0.400000000000 4 1.67 + 0.40 0.425000000000 5 1.74 + 0.40 0.450000000000 5 1.82 + 0.40 0.475000000000 5 1.90 + 0.40 0.500000000000 5 2.00 + 0.50 0.512500000000 6 2.05 + 0.50 0.525000000000 6 2.11 + 0.50 0.537500000000 6 2.16 + 0.50 0.550000000000 6 2.22 + 0.50 0.562500000000 6 2.29 + 0.50 0.575000000000 6 2.35 + 0.50 0.587500000000 6 2.42 + 0.50 0.600000000000 6 2.50 + 0.60 0.612500000000 7 2.58 + 0.60 0.625000000000 7 2.67 + 0.60 0.637500000000 7 2.76 + 0.60 0.650000000000 7 2.86 + 0.60 0.662500000000 7 2.96 + 0.60 0.675000000000 7 3.08 + 0.60 0.687500000000 7 3.20 + 0.60 0.700000000000 7 3.33 + 0.70 0.712500000000 8 3.48 + 0.70 0.725000000000 8 3.64 + 0.70 0.737500000000 8 3.81 + 0.70 0.750000000000 8 4.00 + 0.70 0.756250000000 8 4.10 + 0.70 0.762500000000 8 4.21 + 0.70 0.768750000000 8 4.32 + 0.70 0.775000000000 8 4.44 + 0.70 0.781250000000 8 4.57 + 0.70 0.787500000000 8 4.71 + 0.70 0.793750000000 8 4.85 + 0.70 0.800000000000 8 5.00 + 0.80 0.806250000000 9 5.16 + 0.80 0.812500000000 9 5.33 + 0.80 0.818750000000 9 5.52 + 0.80 0.825000000000 9 5.71 + 0.80 0.831250000000 9 5.93 + 0.80 0.837500000000 9 6.15 + 0.80 0.843750000000 9 6.40 + 0.80 0.850000000000 9 6.67 + 0.80 0.856250000000 9 6.96 + 0.80 0.862500000000 9 7.27 + 0.80 0.868750000000 9 7.62 + 0.80 0.875000000000 9 8.00 + 0.80 0.878125000000 9 8.21 + 0.80 0.881250000000 9 8.42 + 0.80 0.884375000000 9 8.65 + 0.80 0.887500000000 9 8.89 + 0.80 0.890625000000 9 9.14 + 0.80 0.893750000000 9 9.41 + 0.80 0.896875000000 9 9.70 + 0.80 0.900000000000 9 10.00 + 0.90 0.903125000000 10 10.32 + 0.90 1.000000000000 10 +#[Mean = 0.45, StdDeviation = 0.29] +#[Max = 0.90, Total count = 10] +#[Buckets = 29, SubBuckets = 256] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_5percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_5percPerHalfDistance.csv new file mode 100644 index 0000000000..1d369c3375 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_5percPerHalfDistance.csv @@ -0,0 +1,21 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.00,0.000000000000,1,1.00 +0.00,0.100000000000,1,1.11 +0.10,0.200000000000,2,1.25 +0.20,0.300000000000,3,1.43 +0.30,0.400000000000,4,1.67 +0.40,0.500000000000,5,2.00 +0.50,0.550000000000,6,2.22 +0.50,0.600000000000,6,2.50 +0.60,0.650000000000,7,2.86 +0.60,0.700000000000,7,3.33 +0.70,0.750000000000,8,4.00 +0.70,0.775000000000,8,4.44 +0.70,0.800000000000,8,5.00 +0.80,0.825000000000,9,5.71 +0.80,0.850000000000,9,6.67 +0.80,0.875000000000,9,8.00 +0.80,0.887500000000,9,8.89 +0.80,0.900000000000,9,10.00 +0.90,0.912500000000,10,11.43 +0.90,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm new file mode 100644 index 0000000000..336fe3c0da --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_2sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm @@ -0,0 +1,25 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.00 0.000000000000 1 1.00 + 0.00 0.100000000000 1 1.11 + 0.10 0.200000000000 2 1.25 + 0.20 0.300000000000 3 1.43 + 0.30 0.400000000000 4 1.67 + 0.40 0.500000000000 5 2.00 + 0.50 0.550000000000 6 2.22 + 0.50 0.600000000000 6 2.50 + 0.60 0.650000000000 7 2.86 + 0.60 0.700000000000 7 3.33 + 0.70 0.750000000000 8 4.00 + 0.70 0.775000000000 8 4.44 + 0.70 0.800000000000 8 5.00 + 0.80 0.825000000000 9 5.71 + 0.80 0.850000000000 9 6.67 + 0.80 0.875000000000 9 8.00 + 0.80 0.887500000000 9 8.89 + 0.80 0.900000000000 9 10.00 + 0.90 0.912500000000 10 11.43 + 0.90 1.000000000000 10 +#[Mean = 0.45, StdDeviation = 0.29] +#[Max = 0.90, Total count = 10] +#[Buckets = 29, SubBuckets = 256] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_10percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_10percPerHalfDistance.csv new file mode 100644 index 0000000000..bab4708c39 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_10percPerHalfDistance.csv @@ -0,0 +1,38 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.000,0.000000000000,1,1.00 +0.000,0.050000000000,1,1.05 +0.000,0.100000000000,1,1.11 +0.100,0.150000000000,2,1.18 +0.100,0.200000000000,2,1.25 +0.200,0.250000000000,3,1.33 +0.200,0.300000000000,3,1.43 +0.300,0.350000000000,4,1.54 +0.300,0.400000000000,4,1.67 +0.400,0.450000000000,5,1.82 +0.400,0.500000000000,5,2.00 +0.500,0.525000000000,6,2.11 +0.500,0.550000000000,6,2.22 +0.500,0.575000000000,6,2.35 +0.500,0.600000000000,6,2.50 +0.600,0.625000000000,7,2.67 +0.600,0.650000000000,7,2.86 +0.600,0.675000000000,7,3.08 +0.600,0.700000000000,7,3.33 +0.700,0.725000000000,8,3.64 +0.700,0.750000000000,8,4.00 +0.700,0.762500000000,8,4.21 +0.700,0.775000000000,8,4.44 +0.700,0.787500000000,8,4.71 +0.700,0.800000000000,8,5.00 +0.800,0.812500000000,9,5.33 +0.800,0.825000000000,9,5.71 +0.800,0.837500000000,9,6.15 +0.800,0.850000000000,9,6.67 +0.800,0.862500000000,9,7.27 +0.800,0.875000000000,9,8.00 +0.800,0.881250000000,9,8.42 +0.800,0.887500000000,9,8.89 +0.800,0.893750000000,9,9.41 +0.800,0.900000000000,9,10.00 +0.901,0.906250000000,10,10.67 +0.901,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm new file mode 100644 index 0000000000..f37d9481dd --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm @@ -0,0 +1,42 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.000 0.000000000000 1 1.00 + 0.000 0.050000000000 1 1.05 + 0.000 0.100000000000 1 1.11 + 0.100 0.150000000000 2 1.18 + 0.100 0.200000000000 2 1.25 + 0.200 0.250000000000 3 1.33 + 0.200 0.300000000000 3 1.43 + 0.300 0.350000000000 4 1.54 + 0.300 0.400000000000 4 1.67 + 0.400 0.450000000000 5 1.82 + 0.400 0.500000000000 5 2.00 + 0.500 0.525000000000 6 2.11 + 0.500 0.550000000000 6 2.22 + 0.500 0.575000000000 6 2.35 + 0.500 0.600000000000 6 2.50 + 0.600 0.625000000000 7 2.67 + 0.600 0.650000000000 7 2.86 + 0.600 0.675000000000 7 3.08 + 0.600 0.700000000000 7 3.33 + 0.700 0.725000000000 8 3.64 + 0.700 0.750000000000 8 4.00 + 0.700 0.762500000000 8 4.21 + 0.700 0.775000000000 8 4.44 + 0.700 0.787500000000 8 4.71 + 0.700 0.800000000000 8 5.00 + 0.800 0.812500000000 9 5.33 + 0.800 0.825000000000 9 5.71 + 0.800 0.837500000000 9 6.15 + 0.800 0.850000000000 9 6.67 + 0.800 0.862500000000 9 7.27 + 0.800 0.875000000000 9 8.00 + 0.800 0.881250000000 9 8.42 + 0.800 0.887500000000 9 8.89 + 0.800 0.893750000000 9 9.41 + 0.800 0.900000000000 9 10.00 + 0.901 0.906250000000 10 10.67 + 0.901 1.000000000000 10 +#[Mean = 0.450, StdDeviation = 0.287] +#[Max = 0.901, Total count = 10] +#[Buckets = 26, SubBuckets = 2048] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_20percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_20percPerHalfDistance.csv new file mode 100644 index 0000000000..384effc856 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_20percPerHalfDistance.csv @@ -0,0 +1,72 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.000,0.000000000000,1,1.00 +0.000,0.025000000000,1,1.03 +0.000,0.050000000000,1,1.05 +0.000,0.075000000000,1,1.08 +0.000,0.100000000000,1,1.11 +0.100,0.125000000000,2,1.14 +0.100,0.150000000000,2,1.18 +0.100,0.175000000000,2,1.21 +0.100,0.200000000000,2,1.25 +0.200,0.225000000000,3,1.29 +0.200,0.250000000000,3,1.33 +0.200,0.275000000000,3,1.38 +0.200,0.300000000000,3,1.43 +0.300,0.325000000000,4,1.48 +0.300,0.350000000000,4,1.54 +0.300,0.375000000000,4,1.60 +0.300,0.400000000000,4,1.67 +0.400,0.425000000000,5,1.74 +0.400,0.450000000000,5,1.82 +0.400,0.475000000000,5,1.90 +0.400,0.500000000000,5,2.00 +0.500,0.512500000000,6,2.05 +0.500,0.525000000000,6,2.11 +0.500,0.537500000000,6,2.16 +0.500,0.550000000000,6,2.22 +0.500,0.562500000000,6,2.29 +0.500,0.575000000000,6,2.35 +0.500,0.587500000000,6,2.42 +0.500,0.600000000000,6,2.50 +0.600,0.612500000000,7,2.58 +0.600,0.625000000000,7,2.67 +0.600,0.637500000000,7,2.76 +0.600,0.650000000000,7,2.86 +0.600,0.662500000000,7,2.96 +0.600,0.675000000000,7,3.08 +0.600,0.687500000000,7,3.20 +0.600,0.700000000000,7,3.33 +0.700,0.712500000000,8,3.48 +0.700,0.725000000000,8,3.64 +0.700,0.737500000000,8,3.81 +0.700,0.750000000000,8,4.00 +0.700,0.756250000000,8,4.10 +0.700,0.762500000000,8,4.21 +0.700,0.768750000000,8,4.32 +0.700,0.775000000000,8,4.44 +0.700,0.781250000000,8,4.57 +0.700,0.787500000000,8,4.71 +0.700,0.793750000000,8,4.85 +0.700,0.800000000000,8,5.00 +0.800,0.806250000000,9,5.16 +0.800,0.812500000000,9,5.33 +0.800,0.818750000000,9,5.52 +0.800,0.825000000000,9,5.71 +0.800,0.831250000000,9,5.93 +0.800,0.837500000000,9,6.15 +0.800,0.843750000000,9,6.40 +0.800,0.850000000000,9,6.67 +0.800,0.856250000000,9,6.96 +0.800,0.862500000000,9,7.27 +0.800,0.868750000000,9,7.62 +0.800,0.875000000000,9,8.00 +0.800,0.878125000000,9,8.21 +0.800,0.881250000000,9,8.42 +0.800,0.884375000000,9,8.65 +0.800,0.887500000000,9,8.89 +0.800,0.890625000000,9,9.14 +0.800,0.893750000000,9,9.41 +0.800,0.896875000000,9,9.70 +0.800,0.900000000000,9,10.00 +0.901,0.903125000000,10,10.32 +0.901,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm new file mode 100644 index 0000000000..2bc0b63846 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm @@ -0,0 +1,76 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.000 0.000000000000 1 1.00 + 0.000 0.025000000000 1 1.03 + 0.000 0.050000000000 1 1.05 + 0.000 0.075000000000 1 1.08 + 0.000 0.100000000000 1 1.11 + 0.100 0.125000000000 2 1.14 + 0.100 0.150000000000 2 1.18 + 0.100 0.175000000000 2 1.21 + 0.100 0.200000000000 2 1.25 + 0.200 0.225000000000 3 1.29 + 0.200 0.250000000000 3 1.33 + 0.200 0.275000000000 3 1.38 + 0.200 0.300000000000 3 1.43 + 0.300 0.325000000000 4 1.48 + 0.300 0.350000000000 4 1.54 + 0.300 0.375000000000 4 1.60 + 0.300 0.400000000000 4 1.67 + 0.400 0.425000000000 5 1.74 + 0.400 0.450000000000 5 1.82 + 0.400 0.475000000000 5 1.90 + 0.400 0.500000000000 5 2.00 + 0.500 0.512500000000 6 2.05 + 0.500 0.525000000000 6 2.11 + 0.500 0.537500000000 6 2.16 + 0.500 0.550000000000 6 2.22 + 0.500 0.562500000000 6 2.29 + 0.500 0.575000000000 6 2.35 + 0.500 0.587500000000 6 2.42 + 0.500 0.600000000000 6 2.50 + 0.600 0.612500000000 7 2.58 + 0.600 0.625000000000 7 2.67 + 0.600 0.637500000000 7 2.76 + 0.600 0.650000000000 7 2.86 + 0.600 0.662500000000 7 2.96 + 0.600 0.675000000000 7 3.08 + 0.600 0.687500000000 7 3.20 + 0.600 0.700000000000 7 3.33 + 0.700 0.712500000000 8 3.48 + 0.700 0.725000000000 8 3.64 + 0.700 0.737500000000 8 3.81 + 0.700 0.750000000000 8 4.00 + 0.700 0.756250000000 8 4.10 + 0.700 0.762500000000 8 4.21 + 0.700 0.768750000000 8 4.32 + 0.700 0.775000000000 8 4.44 + 0.700 0.781250000000 8 4.57 + 0.700 0.787500000000 8 4.71 + 0.700 0.793750000000 8 4.85 + 0.700 0.800000000000 8 5.00 + 0.800 0.806250000000 9 5.16 + 0.800 0.812500000000 9 5.33 + 0.800 0.818750000000 9 5.52 + 0.800 0.825000000000 9 5.71 + 0.800 0.831250000000 9 5.93 + 0.800 0.837500000000 9 6.15 + 0.800 0.843750000000 9 6.40 + 0.800 0.850000000000 9 6.67 + 0.800 0.856250000000 9 6.96 + 0.800 0.862500000000 9 7.27 + 0.800 0.868750000000 9 7.62 + 0.800 0.875000000000 9 8.00 + 0.800 0.878125000000 9 8.21 + 0.800 0.881250000000 9 8.42 + 0.800 0.884375000000 9 8.65 + 0.800 0.887500000000 9 8.89 + 0.800 0.890625000000 9 9.14 + 0.800 0.893750000000 9 9.41 + 0.800 0.896875000000 9 9.70 + 0.800 0.900000000000 9 10.00 + 0.901 0.903125000000 10 10.32 + 0.901 1.000000000000 10 +#[Mean = 0.450, StdDeviation = 0.287] +#[Max = 0.901, Total count = 10] +#[Buckets = 26, SubBuckets = 2048] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_5percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_5percPerHalfDistance.csv new file mode 100644 index 0000000000..69c6d912ac --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_5percPerHalfDistance.csv @@ -0,0 +1,21 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.000,0.000000000000,1,1.00 +0.000,0.100000000000,1,1.11 +0.100,0.200000000000,2,1.25 +0.200,0.300000000000,3,1.43 +0.300,0.400000000000,4,1.67 +0.400,0.500000000000,5,2.00 +0.500,0.550000000000,6,2.22 +0.500,0.600000000000,6,2.50 +0.600,0.650000000000,7,2.86 +0.600,0.700000000000,7,3.33 +0.700,0.750000000000,8,4.00 +0.700,0.775000000000,8,4.44 +0.700,0.800000000000,8,5.00 +0.800,0.825000000000,9,5.71 +0.800,0.850000000000,9,6.67 +0.800,0.875000000000,9,8.00 +0.800,0.887500000000,9,8.89 +0.800,0.900000000000,9,10.00 +0.901,0.912500000000,10,11.43 +0.901,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm new file mode 100644 index 0000000000..7ae96463e1 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_3sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm @@ -0,0 +1,25 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.000 0.000000000000 1 1.00 + 0.000 0.100000000000 1 1.11 + 0.100 0.200000000000 2 1.25 + 0.200 0.300000000000 3 1.43 + 0.300 0.400000000000 4 1.67 + 0.400 0.500000000000 5 2.00 + 0.500 0.550000000000 6 2.22 + 0.500 0.600000000000 6 2.50 + 0.600 0.650000000000 7 2.86 + 0.600 0.700000000000 7 3.33 + 0.700 0.750000000000 8 4.00 + 0.700 0.775000000000 8 4.44 + 0.700 0.800000000000 8 5.00 + 0.800 0.825000000000 9 5.71 + 0.800 0.850000000000 9 6.67 + 0.800 0.875000000000 9 8.00 + 0.800 0.887500000000 9 8.89 + 0.800 0.900000000000 9 10.00 + 0.901 0.912500000000 10 11.43 + 0.901 1.000000000000 10 +#[Mean = 0.450, StdDeviation = 0.287] +#[Max = 0.901, Total count = 10] +#[Buckets = 26, SubBuckets = 2048] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_10percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_10percPerHalfDistance.csv new file mode 100644 index 0000000000..586e423fd6 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_10percPerHalfDistance.csv @@ -0,0 +1,38 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.0000,0.000000000000,1,1.00 +0.0000,0.050000000000,1,1.05 +0.0000,0.100000000000,1,1.11 +0.1000,0.150000000000,2,1.18 +0.1000,0.200000000000,2,1.25 +0.2000,0.250000000000,3,1.33 +0.2000,0.300000000000,3,1.43 +0.3000,0.350000000000,4,1.54 +0.3000,0.400000000000,4,1.67 +0.4000,0.450000000000,5,1.82 +0.4000,0.500000000000,5,2.00 +0.5000,0.525000000000,6,2.11 +0.5000,0.550000000000,6,2.22 +0.5000,0.575000000000,6,2.35 +0.5000,0.600000000000,6,2.50 +0.6000,0.625000000000,7,2.67 +0.6000,0.650000000000,7,2.86 +0.6000,0.675000000000,7,3.08 +0.6000,0.700000000000,7,3.33 +0.7000,0.725000000000,8,3.64 +0.7000,0.750000000000,8,4.00 +0.7000,0.762500000000,8,4.21 +0.7000,0.775000000000,8,4.44 +0.7000,0.787500000000,8,4.71 +0.7000,0.800000000000,8,5.00 +0.8000,0.812500000000,9,5.33 +0.8000,0.825000000000,9,5.71 +0.8000,0.837500000000,9,6.15 +0.8000,0.850000000000,9,6.67 +0.8000,0.862500000000,9,7.27 +0.8000,0.875000000000,9,8.00 +0.8000,0.881250000000,9,8.42 +0.8000,0.887500000000,9,8.89 +0.8000,0.893750000000,9,9.41 +0.8000,0.900000000000,9,10.00 +0.9000,0.906250000000,10,10.67 +0.9000,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm new file mode 100644 index 0000000000..749f79dd59 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm @@ -0,0 +1,42 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.0000 0.000000000000 1 1.00 + 0.0000 0.050000000000 1 1.05 + 0.0000 0.100000000000 1 1.11 + 0.1000 0.150000000000 2 1.18 + 0.1000 0.200000000000 2 1.25 + 0.2000 0.250000000000 3 1.33 + 0.2000 0.300000000000 3 1.43 + 0.3000 0.350000000000 4 1.54 + 0.3000 0.400000000000 4 1.67 + 0.4000 0.450000000000 5 1.82 + 0.4000 0.500000000000 5 2.00 + 0.5000 0.525000000000 6 2.11 + 0.5000 0.550000000000 6 2.22 + 0.5000 0.575000000000 6 2.35 + 0.5000 0.600000000000 6 2.50 + 0.6000 0.625000000000 7 2.67 + 0.6000 0.650000000000 7 2.86 + 0.6000 0.675000000000 7 3.08 + 0.6000 0.700000000000 7 3.33 + 0.7000 0.725000000000 8 3.64 + 0.7000 0.750000000000 8 4.00 + 0.7000 0.762500000000 8 4.21 + 0.7000 0.775000000000 8 4.44 + 0.7000 0.787500000000 8 4.71 + 0.7000 0.800000000000 8 5.00 + 0.8000 0.812500000000 9 5.33 + 0.8000 0.825000000000 9 5.71 + 0.8000 0.837500000000 9 6.15 + 0.8000 0.850000000000 9 6.67 + 0.8000 0.862500000000 9 7.27 + 0.8000 0.875000000000 9 8.00 + 0.8000 0.881250000000 9 8.42 + 0.8000 0.887500000000 9 8.89 + 0.8000 0.893750000000 9 9.41 + 0.8000 0.900000000000 9 10.00 + 0.9000 0.906250000000 10 10.67 + 0.9000 1.000000000000 10 +#[Mean = 0.4500, StdDeviation = 0.2872] +#[Max = 0.9000, Total count = 10] +#[Buckets = 22, SubBuckets = 32768] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_20percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_20percPerHalfDistance.csv new file mode 100644 index 0000000000..c79155e12b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_20percPerHalfDistance.csv @@ -0,0 +1,72 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.0000,0.000000000000,1,1.00 +0.0000,0.025000000000,1,1.03 +0.0000,0.050000000000,1,1.05 +0.0000,0.075000000000,1,1.08 +0.0000,0.100000000000,1,1.11 +0.1000,0.125000000000,2,1.14 +0.1000,0.150000000000,2,1.18 +0.1000,0.175000000000,2,1.21 +0.1000,0.200000000000,2,1.25 +0.2000,0.225000000000,3,1.29 +0.2000,0.250000000000,3,1.33 +0.2000,0.275000000000,3,1.38 +0.2000,0.300000000000,3,1.43 +0.3000,0.325000000000,4,1.48 +0.3000,0.350000000000,4,1.54 +0.3000,0.375000000000,4,1.60 +0.3000,0.400000000000,4,1.67 +0.4000,0.425000000000,5,1.74 +0.4000,0.450000000000,5,1.82 +0.4000,0.475000000000,5,1.90 +0.4000,0.500000000000,5,2.00 +0.5000,0.512500000000,6,2.05 +0.5000,0.525000000000,6,2.11 +0.5000,0.537500000000,6,2.16 +0.5000,0.550000000000,6,2.22 +0.5000,0.562500000000,6,2.29 +0.5000,0.575000000000,6,2.35 +0.5000,0.587500000000,6,2.42 +0.5000,0.600000000000,6,2.50 +0.6000,0.612500000000,7,2.58 +0.6000,0.625000000000,7,2.67 +0.6000,0.637500000000,7,2.76 +0.6000,0.650000000000,7,2.86 +0.6000,0.662500000000,7,2.96 +0.6000,0.675000000000,7,3.08 +0.6000,0.687500000000,7,3.20 +0.6000,0.700000000000,7,3.33 +0.7000,0.712500000000,8,3.48 +0.7000,0.725000000000,8,3.64 +0.7000,0.737500000000,8,3.81 +0.7000,0.750000000000,8,4.00 +0.7000,0.756250000000,8,4.10 +0.7000,0.762500000000,8,4.21 +0.7000,0.768750000000,8,4.32 +0.7000,0.775000000000,8,4.44 +0.7000,0.781250000000,8,4.57 +0.7000,0.787500000000,8,4.71 +0.7000,0.793750000000,8,4.85 +0.7000,0.800000000000,8,5.00 +0.8000,0.806250000000,9,5.16 +0.8000,0.812500000000,9,5.33 +0.8000,0.818750000000,9,5.52 +0.8000,0.825000000000,9,5.71 +0.8000,0.831250000000,9,5.93 +0.8000,0.837500000000,9,6.15 +0.8000,0.843750000000,9,6.40 +0.8000,0.850000000000,9,6.67 +0.8000,0.856250000000,9,6.96 +0.8000,0.862500000000,9,7.27 +0.8000,0.868750000000,9,7.62 +0.8000,0.875000000000,9,8.00 +0.8000,0.878125000000,9,8.21 +0.8000,0.881250000000,9,8.42 +0.8000,0.884375000000,9,8.65 +0.8000,0.887500000000,9,8.89 +0.8000,0.890625000000,9,9.14 +0.8000,0.893750000000,9,9.41 +0.8000,0.896875000000,9,9.70 +0.8000,0.900000000000,9,10.00 +0.9000,0.903125000000,10,10.32 +0.9000,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm new file mode 100644 index 0000000000..e611fd135d --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm @@ -0,0 +1,76 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.0000 0.000000000000 1 1.00 + 0.0000 0.025000000000 1 1.03 + 0.0000 0.050000000000 1 1.05 + 0.0000 0.075000000000 1 1.08 + 0.0000 0.100000000000 1 1.11 + 0.1000 0.125000000000 2 1.14 + 0.1000 0.150000000000 2 1.18 + 0.1000 0.175000000000 2 1.21 + 0.1000 0.200000000000 2 1.25 + 0.2000 0.225000000000 3 1.29 + 0.2000 0.250000000000 3 1.33 + 0.2000 0.275000000000 3 1.38 + 0.2000 0.300000000000 3 1.43 + 0.3000 0.325000000000 4 1.48 + 0.3000 0.350000000000 4 1.54 + 0.3000 0.375000000000 4 1.60 + 0.3000 0.400000000000 4 1.67 + 0.4000 0.425000000000 5 1.74 + 0.4000 0.450000000000 5 1.82 + 0.4000 0.475000000000 5 1.90 + 0.4000 0.500000000000 5 2.00 + 0.5000 0.512500000000 6 2.05 + 0.5000 0.525000000000 6 2.11 + 0.5000 0.537500000000 6 2.16 + 0.5000 0.550000000000 6 2.22 + 0.5000 0.562500000000 6 2.29 + 0.5000 0.575000000000 6 2.35 + 0.5000 0.587500000000 6 2.42 + 0.5000 0.600000000000 6 2.50 + 0.6000 0.612500000000 7 2.58 + 0.6000 0.625000000000 7 2.67 + 0.6000 0.637500000000 7 2.76 + 0.6000 0.650000000000 7 2.86 + 0.6000 0.662500000000 7 2.96 + 0.6000 0.675000000000 7 3.08 + 0.6000 0.687500000000 7 3.20 + 0.6000 0.700000000000 7 3.33 + 0.7000 0.712500000000 8 3.48 + 0.7000 0.725000000000 8 3.64 + 0.7000 0.737500000000 8 3.81 + 0.7000 0.750000000000 8 4.00 + 0.7000 0.756250000000 8 4.10 + 0.7000 0.762500000000 8 4.21 + 0.7000 0.768750000000 8 4.32 + 0.7000 0.775000000000 8 4.44 + 0.7000 0.781250000000 8 4.57 + 0.7000 0.787500000000 8 4.71 + 0.7000 0.793750000000 8 4.85 + 0.7000 0.800000000000 8 5.00 + 0.8000 0.806250000000 9 5.16 + 0.8000 0.812500000000 9 5.33 + 0.8000 0.818750000000 9 5.52 + 0.8000 0.825000000000 9 5.71 + 0.8000 0.831250000000 9 5.93 + 0.8000 0.837500000000 9 6.15 + 0.8000 0.843750000000 9 6.40 + 0.8000 0.850000000000 9 6.67 + 0.8000 0.856250000000 9 6.96 + 0.8000 0.862500000000 9 7.27 + 0.8000 0.868750000000 9 7.62 + 0.8000 0.875000000000 9 8.00 + 0.8000 0.878125000000 9 8.21 + 0.8000 0.881250000000 9 8.42 + 0.8000 0.884375000000 9 8.65 + 0.8000 0.887500000000 9 8.89 + 0.8000 0.890625000000 9 9.14 + 0.8000 0.893750000000 9 9.41 + 0.8000 0.896875000000 9 9.70 + 0.8000 0.900000000000 9 10.00 + 0.9000 0.903125000000 10 10.32 + 0.9000 1.000000000000 10 +#[Mean = 0.4500, StdDeviation = 0.2872] +#[Max = 0.9000, Total count = 10] +#[Buckets = 22, SubBuckets = 32768] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_5percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_5percPerHalfDistance.csv new file mode 100644 index 0000000000..cc14b0a3b2 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_5percPerHalfDistance.csv @@ -0,0 +1,21 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.0000,0.000000000000,1,1.00 +0.0000,0.100000000000,1,1.11 +0.1000,0.200000000000,2,1.25 +0.2000,0.300000000000,3,1.43 +0.3000,0.400000000000,4,1.67 +0.4000,0.500000000000,5,2.00 +0.5000,0.550000000000,6,2.22 +0.5000,0.600000000000,6,2.50 +0.6000,0.650000000000,7,2.86 +0.6000,0.700000000000,7,3.33 +0.7000,0.750000000000,8,4.00 +0.7000,0.775000000000,8,4.44 +0.7000,0.800000000000,8,5.00 +0.8000,0.825000000000,9,5.71 +0.8000,0.850000000000,9,6.67 +0.8000,0.875000000000,9,8.00 +0.8000,0.887500000000,9,8.89 +0.8000,0.900000000000,9,10.00 +0.9000,0.912500000000,10,11.43 +0.9000,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm new file mode 100644 index 0000000000..1cae9d7621 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_4sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm @@ -0,0 +1,25 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.0000 0.000000000000 1 1.00 + 0.0000 0.100000000000 1 1.11 + 0.1000 0.200000000000 2 1.25 + 0.2000 0.300000000000 3 1.43 + 0.3000 0.400000000000 4 1.67 + 0.4000 0.500000000000 5 2.00 + 0.5000 0.550000000000 6 2.22 + 0.5000 0.600000000000 6 2.50 + 0.6000 0.650000000000 7 2.86 + 0.6000 0.700000000000 7 3.33 + 0.7000 0.750000000000 8 4.00 + 0.7000 0.775000000000 8 4.44 + 0.7000 0.800000000000 8 5.00 + 0.8000 0.825000000000 9 5.71 + 0.8000 0.850000000000 9 6.67 + 0.8000 0.875000000000 9 8.00 + 0.8000 0.887500000000 9 8.89 + 0.8000 0.900000000000 9 10.00 + 0.9000 0.912500000000 10 11.43 + 0.9000 1.000000000000 10 +#[Mean = 0.4500, StdDeviation = 0.2872] +#[Max = 0.9000, Total count = 10] +#[Buckets = 22, SubBuckets = 32768] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_10percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_10percPerHalfDistance.csv new file mode 100644 index 0000000000..fdb40f3e5a --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_10percPerHalfDistance.csv @@ -0,0 +1,38 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.00000,0.000000000000,1,1.00 +0.00000,0.050000000000,1,1.05 +0.00000,0.100000000000,1,1.11 +0.10000,0.150000000000,2,1.18 +0.10000,0.200000000000,2,1.25 +0.20000,0.250000000000,3,1.33 +0.20000,0.300000000000,3,1.43 +0.30000,0.350000000000,4,1.54 +0.30000,0.400000000000,4,1.67 +0.40000,0.450000000000,5,1.82 +0.40000,0.500000000000,5,2.00 +0.50000,0.525000000000,6,2.11 +0.50000,0.550000000000,6,2.22 +0.50000,0.575000000000,6,2.35 +0.50000,0.600000000000,6,2.50 +0.60000,0.625000000000,7,2.67 +0.60000,0.650000000000,7,2.86 +0.60000,0.675000000000,7,3.08 +0.60000,0.700000000000,7,3.33 +0.70000,0.725000000000,8,3.64 +0.70000,0.750000000000,8,4.00 +0.70000,0.762500000000,8,4.21 +0.70000,0.775000000000,8,4.44 +0.70000,0.787500000000,8,4.71 +0.70000,0.800000000000,8,5.00 +0.80000,0.812500000000,9,5.33 +0.80000,0.825000000000,9,5.71 +0.80000,0.837500000000,9,6.15 +0.80000,0.850000000000,9,6.67 +0.80000,0.862500000000,9,7.27 +0.80000,0.875000000000,9,8.00 +0.80000,0.881250000000,9,8.42 +0.80000,0.887500000000,9,8.89 +0.80000,0.893750000000,9,9.41 +0.80000,0.900000000000,9,10.00 +0.90000,0.906250000000,10,10.67 +0.90000,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm new file mode 100644 index 0000000000..9fe6e77f7c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_10percPerHalfDistance.hgrm @@ -0,0 +1,42 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.00000 0.000000000000 1 1.00 + 0.00000 0.050000000000 1 1.05 + 0.00000 0.100000000000 1 1.11 + 0.10000 0.150000000000 2 1.18 + 0.10000 0.200000000000 2 1.25 + 0.20000 0.250000000000 3 1.33 + 0.20000 0.300000000000 3 1.43 + 0.30000 0.350000000000 4 1.54 + 0.30000 0.400000000000 4 1.67 + 0.40000 0.450000000000 5 1.82 + 0.40000 0.500000000000 5 2.00 + 0.50000 0.525000000000 6 2.11 + 0.50000 0.550000000000 6 2.22 + 0.50000 0.575000000000 6 2.35 + 0.50000 0.600000000000 6 2.50 + 0.60000 0.625000000000 7 2.67 + 0.60000 0.650000000000 7 2.86 + 0.60000 0.675000000000 7 3.08 + 0.60000 0.700000000000 7 3.33 + 0.70000 0.725000000000 8 3.64 + 0.70000 0.750000000000 8 4.00 + 0.70000 0.762500000000 8 4.21 + 0.70000 0.775000000000 8 4.44 + 0.70000 0.787500000000 8 4.71 + 0.70000 0.800000000000 8 5.00 + 0.80000 0.812500000000 9 5.33 + 0.80000 0.825000000000 9 5.71 + 0.80000 0.837500000000 9 6.15 + 0.80000 0.850000000000 9 6.67 + 0.80000 0.862500000000 9 7.27 + 0.80000 0.875000000000 9 8.00 + 0.80000 0.881250000000 9 8.42 + 0.80000 0.887500000000 9 8.89 + 0.80000 0.893750000000 9 9.41 + 0.80000 0.900000000000 9 10.00 + 0.90000 0.906250000000 10 10.67 + 0.90000 1.000000000000 10 +#[Mean = 0.45000, StdDeviation = 0.28723] +#[Max = 0.90000, Total count = 10] +#[Buckets = 19, SubBuckets = 262144] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_20percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_20percPerHalfDistance.csv new file mode 100644 index 0000000000..04d9e8dead --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_20percPerHalfDistance.csv @@ -0,0 +1,72 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.00000,0.000000000000,1,1.00 +0.00000,0.025000000000,1,1.03 +0.00000,0.050000000000,1,1.05 +0.00000,0.075000000000,1,1.08 +0.00000,0.100000000000,1,1.11 +0.10000,0.125000000000,2,1.14 +0.10000,0.150000000000,2,1.18 +0.10000,0.175000000000,2,1.21 +0.10000,0.200000000000,2,1.25 +0.20000,0.225000000000,3,1.29 +0.20000,0.250000000000,3,1.33 +0.20000,0.275000000000,3,1.38 +0.20000,0.300000000000,3,1.43 +0.30000,0.325000000000,4,1.48 +0.30000,0.350000000000,4,1.54 +0.30000,0.375000000000,4,1.60 +0.30000,0.400000000000,4,1.67 +0.40000,0.425000000000,5,1.74 +0.40000,0.450000000000,5,1.82 +0.40000,0.475000000000,5,1.90 +0.40000,0.500000000000,5,2.00 +0.50000,0.512500000000,6,2.05 +0.50000,0.525000000000,6,2.11 +0.50000,0.537500000000,6,2.16 +0.50000,0.550000000000,6,2.22 +0.50000,0.562500000000,6,2.29 +0.50000,0.575000000000,6,2.35 +0.50000,0.587500000000,6,2.42 +0.50000,0.600000000000,6,2.50 +0.60000,0.612500000000,7,2.58 +0.60000,0.625000000000,7,2.67 +0.60000,0.637500000000,7,2.76 +0.60000,0.650000000000,7,2.86 +0.60000,0.662500000000,7,2.96 +0.60000,0.675000000000,7,3.08 +0.60000,0.687500000000,7,3.20 +0.60000,0.700000000000,7,3.33 +0.70000,0.712500000000,8,3.48 +0.70000,0.725000000000,8,3.64 +0.70000,0.737500000000,8,3.81 +0.70000,0.750000000000,8,4.00 +0.70000,0.756250000000,8,4.10 +0.70000,0.762500000000,8,4.21 +0.70000,0.768750000000,8,4.32 +0.70000,0.775000000000,8,4.44 +0.70000,0.781250000000,8,4.57 +0.70000,0.787500000000,8,4.71 +0.70000,0.793750000000,8,4.85 +0.70000,0.800000000000,8,5.00 +0.80000,0.806250000000,9,5.16 +0.80000,0.812500000000,9,5.33 +0.80000,0.818750000000,9,5.52 +0.80000,0.825000000000,9,5.71 +0.80000,0.831250000000,9,5.93 +0.80000,0.837500000000,9,6.15 +0.80000,0.843750000000,9,6.40 +0.80000,0.850000000000,9,6.67 +0.80000,0.856250000000,9,6.96 +0.80000,0.862500000000,9,7.27 +0.80000,0.868750000000,9,7.62 +0.80000,0.875000000000,9,8.00 +0.80000,0.878125000000,9,8.21 +0.80000,0.881250000000,9,8.42 +0.80000,0.884375000000,9,8.65 +0.80000,0.887500000000,9,8.89 +0.80000,0.890625000000,9,9.14 +0.80000,0.893750000000,9,9.41 +0.80000,0.896875000000,9,9.70 +0.80000,0.900000000000,9,10.00 +0.90000,0.903125000000,10,10.32 +0.90000,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm new file mode 100644 index 0000000000..2e1827f97a --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_20percPerHalfDistance.hgrm @@ -0,0 +1,76 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.00000 0.000000000000 1 1.00 + 0.00000 0.025000000000 1 1.03 + 0.00000 0.050000000000 1 1.05 + 0.00000 0.075000000000 1 1.08 + 0.00000 0.100000000000 1 1.11 + 0.10000 0.125000000000 2 1.14 + 0.10000 0.150000000000 2 1.18 + 0.10000 0.175000000000 2 1.21 + 0.10000 0.200000000000 2 1.25 + 0.20000 0.225000000000 3 1.29 + 0.20000 0.250000000000 3 1.33 + 0.20000 0.275000000000 3 1.38 + 0.20000 0.300000000000 3 1.43 + 0.30000 0.325000000000 4 1.48 + 0.30000 0.350000000000 4 1.54 + 0.30000 0.375000000000 4 1.60 + 0.30000 0.400000000000 4 1.67 + 0.40000 0.425000000000 5 1.74 + 0.40000 0.450000000000 5 1.82 + 0.40000 0.475000000000 5 1.90 + 0.40000 0.500000000000 5 2.00 + 0.50000 0.512500000000 6 2.05 + 0.50000 0.525000000000 6 2.11 + 0.50000 0.537500000000 6 2.16 + 0.50000 0.550000000000 6 2.22 + 0.50000 0.562500000000 6 2.29 + 0.50000 0.575000000000 6 2.35 + 0.50000 0.587500000000 6 2.42 + 0.50000 0.600000000000 6 2.50 + 0.60000 0.612500000000 7 2.58 + 0.60000 0.625000000000 7 2.67 + 0.60000 0.637500000000 7 2.76 + 0.60000 0.650000000000 7 2.86 + 0.60000 0.662500000000 7 2.96 + 0.60000 0.675000000000 7 3.08 + 0.60000 0.687500000000 7 3.20 + 0.60000 0.700000000000 7 3.33 + 0.70000 0.712500000000 8 3.48 + 0.70000 0.725000000000 8 3.64 + 0.70000 0.737500000000 8 3.81 + 0.70000 0.750000000000 8 4.00 + 0.70000 0.756250000000 8 4.10 + 0.70000 0.762500000000 8 4.21 + 0.70000 0.768750000000 8 4.32 + 0.70000 0.775000000000 8 4.44 + 0.70000 0.781250000000 8 4.57 + 0.70000 0.787500000000 8 4.71 + 0.70000 0.793750000000 8 4.85 + 0.70000 0.800000000000 8 5.00 + 0.80000 0.806250000000 9 5.16 + 0.80000 0.812500000000 9 5.33 + 0.80000 0.818750000000 9 5.52 + 0.80000 0.825000000000 9 5.71 + 0.80000 0.831250000000 9 5.93 + 0.80000 0.837500000000 9 6.15 + 0.80000 0.843750000000 9 6.40 + 0.80000 0.850000000000 9 6.67 + 0.80000 0.856250000000 9 6.96 + 0.80000 0.862500000000 9 7.27 + 0.80000 0.868750000000 9 7.62 + 0.80000 0.875000000000 9 8.00 + 0.80000 0.878125000000 9 8.21 + 0.80000 0.881250000000 9 8.42 + 0.80000 0.884375000000 9 8.65 + 0.80000 0.887500000000 9 8.89 + 0.80000 0.890625000000 9 9.14 + 0.80000 0.893750000000 9 9.41 + 0.80000 0.896875000000 9 9.70 + 0.80000 0.900000000000 9 10.00 + 0.90000 0.903125000000 10 10.32 + 0.90000 1.000000000000 10 +#[Mean = 0.45000, StdDeviation = 0.28723] +#[Max = 0.90000, Total count = 10] +#[Buckets = 19, SubBuckets = 262144] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_5percPerHalfDistance.csv b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_5percPerHalfDistance.csv new file mode 100644 index 0000000000..27ffd09154 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_5percPerHalfDistance.csv @@ -0,0 +1,21 @@ +"Value","Percentile","TotalCount","1/(1-Percentile)" +0.00000,0.000000000000,1,1.00 +0.00000,0.100000000000,1,1.11 +0.10000,0.200000000000,2,1.25 +0.20000,0.300000000000,3,1.43 +0.30000,0.400000000000,4,1.67 +0.40000,0.500000000000,5,2.00 +0.50000,0.550000000000,6,2.22 +0.50000,0.600000000000,6,2.50 +0.60000,0.650000000000,7,2.86 +0.60000,0.700000000000,7,3.33 +0.70000,0.750000000000,8,4.00 +0.70000,0.775000000000,8,4.44 +0.70000,0.800000000000,8,5.00 +0.80000,0.825000000000,9,5.71 +0.80000,0.850000000000,9,6.67 +0.80000,0.875000000000,9,8.00 +0.80000,0.887500000000,9,8.89 +0.80000,0.900000000000,9,10.00 +0.90000,0.912500000000,10,11.43 +0.90000,1.000000000000,10,Infinity diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm new file mode 100644 index 0000000000..b6264230c9 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/Sample_10kBy1k_5sf_TicksPerHour_asMs_5percPerHalfDistance.hgrm @@ -0,0 +1,25 @@ + Value Percentile TotalCount 1/(1-Percentile) + + 0.00000 0.000000000000 1 1.00 + 0.00000 0.100000000000 1 1.11 + 0.10000 0.200000000000 2 1.25 + 0.20000 0.300000000000 3 1.43 + 0.30000 0.400000000000 4 1.67 + 0.40000 0.500000000000 5 2.00 + 0.50000 0.550000000000 6 2.22 + 0.50000 0.600000000000 6 2.50 + 0.60000 0.650000000000 7 2.86 + 0.60000 0.700000000000 7 3.33 + 0.70000 0.750000000000 8 4.00 + 0.70000 0.775000000000 8 4.44 + 0.70000 0.800000000000 8 5.00 + 0.80000 0.825000000000 9 5.71 + 0.80000 0.850000000000 9 6.67 + 0.80000 0.875000000000 9 8.00 + 0.80000 0.887500000000 9 8.89 + 0.80000 0.900000000000 9 10.00 + 0.90000 0.912500000000 10 11.43 + 0.90000 1.000000000000 10 +#[Mean = 0.45000, StdDeviation = 0.28723] +#[Max = 0.90000, Total count = 10] +#[Buckets = 19, SubBuckets = 262144] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.1.logV0.hlog b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.1.logV0.hlog new file mode 100644 index 0000000000..e38e3eca79 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.1.logV0.hlog @@ -0,0 +1,85 @@ +#[Logged with jHiccup version 2.0.1] +#[Histogram log format version 1.01] +#[StartTime: 1438869961.225 (seconds since epoch), Thu Aug 06 07:06:01 PDT 2015] +"StartTimestamp","EndTimestamp","Interval_Max","Interval_Compressed_Histogram" +0.116,1.005,3.031,HISTiQAAAG542pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI+gDFYozQmlOaA0M5Rmg9LsUJoFjc+GZg4rmjkwdUxofG4obQeldaG0LJTmR1PvCKXVIRQjzL2BUFoYTT0jwyjABkbDZTSeR8FofI+CEQ4AUBEGxA== +1.121,1.000,0.442,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA+hDEY0mhVKc0BpdijNAqW50NSx4dDPhqafDYf5OlDaGEpLQmleNPs8oLQy1LoYVD5cPcydUH+PglEwCkbBKBgFowAZAABAZwbC +2.121,0.999,0.459,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcYsANmNJoNB82Jpo4Fjc8NpVlhFkJpLijNDqV1obQGlFZEMx+mLghKK0AoxigoXxVKi0NpRjT/MDKMglEwCkbBKBgFowAOAAw5Brw= +3.120,1.002,0.442,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPehDEYozYxGs0BpVjQ+F5o+dhz6OWAWoZkDU88GpXWhtCqU1kDTD9MXAqXVodb7QvlmUJoXzZ0w942CUTAKRsEoGAWjAAkAABrTBr4= +4.122,1.000,0.475,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcYsAOoegY2ND6MZoHSXFCaHU09jM+Kpp4NB60DpfWhtByU5kYzLxBK60EoRncoXwNKS0BpZhz+YmQYBaNgFIyCUTAKRgEDAArJBrw= +5.122,1.000,0.442,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHcZUAEjTAJKs0BpNjRxGJ8dSnOgibPioFlw8JWhtBqUloXSPFCaC0qHQWltqHPDoXwtKC2MZj4jwygYBaNgFIyCUTAKMAAA+GAGug== +6.122,1.001,0.459,HISTiQAAAGd42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHcZsANGmAIozQKlWaE0M5RmQxOHqeNAU8eOpp4Lja8JpbWhtC6U5kQzPwRKa0CdaQPlm0JpYTRzGdHoUTAKRsEoGAWjYBQAAQD4sAa6 +7.123,0.997,0.541,HISTiQAAAGt42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI+gDBYozYhGs6HJw/isaOpY0dTBaA4ozQmlmdHkeaG0HZR2gdKKUJodzV5VKO0Kpe9D6UooLQaludDcRywgVf0oGAWjYBSMglEwJAEAhtAHww== +8.120,1.001,0.459,HISTiQAAAGJ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHcZsANGKM0MUwilWXHQMHXsaHwWNBomzonGl4PSulBaEUoLoZkrD6XdofQbKL0CSsuiqUf3zygYBaNgFIyCUTAKgAAAJX4HuQ== +9.121,0.999,0.442,HISTiQAAAGJ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI+hDBY0mhWNhomzodEweSYc5jDjMIcTjTaC0i5QWhVK86Cpk4DSDlD6NZSuh9KSUJoDSjMyjIJRMApGwSgYBaMAAwAAlsoHxQ== +10.120,1.001,0.459,HISTiQAAAGB42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8ZUAETGs0KpZmhNDsazYKmDpd+GM2NQ582lFZDoznR7FeE0iEQilEKyi+E0vxo+mCAkWEUjIJRMApGwSgYBXAAADJXBsI= +11.121,1.000,0.442,HISTiQAAAGh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPegDEYozQyTgNLsaOLMaOpZ0NSzQmkuHPIw8zjRzFOB0jpQWhFK80NpHigtDaVdoPR3KD0dSkug2Q9z5ygYBaNgFIyCUTAKkAAAOxAHuw== +12.121,2.238,1568.670,HISTiQAAAOl42u2aMQ7CMAxFnZaqILEhISQYOAIjJ2Bk40YM7FyAMzBxPCRwhkYqlUpTTPL+8uXEdv6312zO14uIFPLCcStSHnaP2zuU6VyacAF7VMraRyZBXAfnZcBVy71/Z6m8Vl55gUG+17tXviuflBct+sAwcIyAPQP2DNgz/vGNX3wb1OUy3QPzxreF/r+qz+1ddDOvGHWu572L1DdVXcyTPVvS5TrqrMTozEMn8xtHpzUujOtDJ3NMSV/Rk8sv6/+tH77pl6JO/18m/KfTxtbzUvPDfPAdM8//s5t1xGPloWeY+loA+IAnHpcMHQ== +14.359,0.761,0.459,HISTiQAAAGp42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTFZQBhuUZsHBZ4bSHFCaFY2Gms/AjqafFYe5MPP4oLQWlLaB0mJQWhhKc0FpfihtBKX7oHQilBZAswfmLkaGUTAKRsEoGAWjYBTAAQCDmgZz +15.120,1.000,0.442,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcZUAEjGs2GRnOi8VmgNDManxtNnBlNH9Q9DOxQWhBK60FpeTRxDijNC6XDofRpKH0JSguj2c/IMApGwSgYBaNgFIwCDAAAR8IHvQ== +16.120,1.002,0.475,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPehDEY0mhmNZoHS7Gg0CxoNU8+GQx4mzoTGl4PSulBaA0qLQGluKM0Ppb2g9EYofRdKi6Kph9nLgOa/UTAKRsEoGAWjYEQDAEgCB70= +17.122,1.001,0.475,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTE8ZUAEjlGaFKUCj2aA0N5RmgdLsaPIsOPgcaHyYuTZQ2hBKq0BpUSjNCaWFobQvlD4IpVdCaXEozYXmH3R6FIyCUTAKRsEoGNEAAK+GB8k= +18.123,1.001,0.426,HISTiQAAAGJ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8ZsAOoegYWHDQblGZFo7mhNCeU5sKhnx2NlofSZlBaEUqLQmkOKC0EpcOg9H4ovQVKy6G5ZxSMglEwCkbBKBgFWAAAjYoHxQ== +19.124,0.996,0.426,HISTiQAAAGN42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTE9gDCjNBqVZ0GhmKM2KQx2MzwmleaA0OxrNhGYeTL0hlHaC0gY4zOOH0ilQeh2U7oHSMlCai2EUjIJRMApGwSgYBTgBAKssB8c= +20.120,1.003,0.459,HISTiQAAAGh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8ZUAFUHQMjlGZBo1mhNDsOPgeaek4ozQyluaA0N5p+ESitC6W10NTB3CUMpUOh9EYofQFKy6PZw4zmn1EwCkbBKBgFo2AUAAEAiFIHxQ== +21.123,0.997,0.442,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHdhDCjNiMaH0cxQmgNNnAuNzwalWdHMY0ETR1cnAaUdoLQylBZGswfG94bSZ6H0DigtCqV50OwfBaNgFIyCUTAKRgESAAArzge5 +22.120,1.000,0.475,HISTiQAAAGh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTO+gDBYozQylWaE0B5o8D5RmQ6PZoTQnmn6YODcanw3NvEQo7QGlZaA0L5r9YlDaHEp3Q+kVaPLo+mCAkWEUjIJRMApGwSgYBQwAU9cH2w== +23.120,1.002,0.442,HISTiQAAAGd42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8ZUAFUHQMLGs0OpdmgNCuaOIzPCaWZ0fSzotFsaPaJQWkdKC0BpXnQaFEoHQulL0DpTVBaBkoLobljFIyCUTAKRsEoGAVIAABnpgfB +24.122,1.001,0.475,HISTiQAAAGh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTM9gDDSahQEVsKGJc0JpdhzizFCaFUrzQGkONH1cUNoISjtDaTUoLYCmng9Ku0Dp+VD6KJQWgtL8DNgBI8MoGAWjYBSMglEwChgAxMAHyw== +25.123,0.997,0.459,HISTiQAAAGt42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8ZsANmKM0CpbmgNCeUZofSrGh8DijNhmYejM+NZh7MfBkobQyllaE0H5q9wlDaD0qfh9KroLQ4mvlQfzMwMoyCUTAKRsEoGAWjAA4AcjYHwQ== +26.120,1.003,0.426,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTE8ZUAEjlGZFo5mhNBuU5oTSLGg0O5o+FjQ+OxoNk1eF0gZQ2gRK86GpF4LSPlB6I5Q+BaVFoTQvwygYBaNgFIyCUTAKcAIArGYHyQ== +27.123,0.997,0.475,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcZUAEzGs0KpVmgNBuaOBcanxNNHQuaPIxmh9KMUFoMSutBaWUoLYLmHgkoHQ2l70DpJWjqOdHMR6dHwSgYBaNgFIyCEQ0ATtIHvQ== +28.120,1.004,0.623,HISTiQAAAG142pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8ZUAEzlGaDKYDSrGjyXFCaHU2eA43PiKaPBc1cmD2SUNoUSqtDaX40+0SgdCCU3g+lT0JpYSjNg2Y/Aw4+uYBa5oyCUTAKRsEoGAUDAgCJQgfF +29.124,0.997,0.459,HISTiQAAAGd42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTM+gDGYozYJGc+CQZ4XS3FCaHUpzouljQ1PPhibPB6UNobQmlDZBM48LSotBaTcovQFKb4fSPGg0DDAyjIJRMApGwSgYBaMADgDKMAfL +30.121,0.999,0.475,HISTiQAAAG542pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8YUAELlGZG48NoqDkMvFCaE0qz46A5oDQblOZCMx+mTgpKG0FpTSgtAKUFobQYlHaC0jCPHIfSslCaD0ozormbkWEUjIJRMApGwSgYBQwAgpgHww== +31.120,1.000,0.475,HISTiQAAAGp42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8YUAEzlGaEKUATZ4PS7FCaBU0dB5RmRaNZ0PSxoYnLQmlrKK0GpfnQzJeG0o5Qei+UvgClJaE0J5q7GdD8NQpGwSgYBaNgFIxoAABYbAe/ +32.120,1.004,0.459,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8ZUAEjlGaB0qxoNNQcBnY0deji6Hx0c2D6mKG0JJRWh9JKUFoQSnNCaSEonQil10HpJ1BaHIc7GBlGwSgYBaNgFIyCUQAHAGMmB8E= +33.124,0.996,0.492,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA+gDEY0mgVKM0NpNhw0K5Rmh9KcUJoDTR07DhomrwSlbaC0Mpo5MPVCUNoPSh+H0mugtCiU5oF5kAE7YGQYBaNgFIyCUTAKRjAAAGPcB78= +34.120,1.003,0.442,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTG+gDGYozYJGw8Q5oDQnlGZDk0cXZ0cT50QzlxVKc0NpHSjtAqWNobQgmj5xKG0NpTdD6RVQWhhK88E8yDAKRsEoGAWjYBSMAgwAACgzB9c= +35.123,0.998,0.475,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPdhDCjNiEazQGkOKM2KRjOj0Rxo5rGjqUc3ByYvC6X1obQulBaG0mxQWhRKu0LpLVD6MpQWgdJcaO5iQPPXKBgFo2AUjIJRMKIBAEwqB70= +36.121,1.003,0.475,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTE8ZUAELGg3Vx8CBJs4NpdmhNCuUZkZTx4qmDibOiWauHJQ2htLqUFoYSnNBaSEoHQylD0DpVWjmwNzHiIMeBaNgFIyCUTAKRjQAAK6eB8k= +37.124,0.998,0.442,HISTiQAAAGp42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8YUAEHlGaB0sxQmg2NZofSrGg0Gxofpo4LzVwONFoBShtCaTUozQulOaG0EJQOgNI7oPRuKC0CpflgHmQYBaNgFIyCUTAKRgEGAACDQAfD +38.122,1.001,0.524,HISTiQAAAGt42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI+hDEYozQKl2dDE2dBomDpWKM2MQ54dSnOgyTOh8SWhtD6UVoPSvGjmSEDpFCj9GUo3Q2lhKM2D5i6YP3ABQvKjYBSMglEwCkbBsAIAi7IHxQ== +39.123,1.001,0.475,HISTiQAAAG542pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTO8YUAELlGaD0pxQmhtK80JpDjR1glCaC0ozo5nHgWYeE5q4LpQOhdLKUFoYSvNAaQko7QylJ0LpZWjqYe5kRPMfOn8UjIJRMApGwSgYkQAAVCcH2w== +40.124,1.000,0.442,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTC+hDEY0mhWN5ofSXFCaDUpzQGl2KM2MQ5wDh7kwcxSgtBmUNobSslBaCEpLQukgKL0FSvdCaREozQmlWRhGwSgYBaNgFIyCUYABAPuGB9E= +41.124,0.996,0.475,HISTiQAAAGd42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI+hDGaYAJRmgdJsUJoDjWZB08eFJs+OJg8TZ8WhThNKG0FpPSjNj0ZLQGlvKL0ISm+D0mJQmhPNP4xo9CgYBaNgFIyCUTCiAQCYogfF +42.120,1.004,0.475,HISTiQAAAGp42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTC9hDCjNCqXZ0PgsOMRhfHYozYEmzwyledDkWdDUKUBpWyhtCqXFoDQXlBaC0m5QeiGU3gmlRdHUwfwFA4wMo2AUjIJRMApGwShgAADzjgfR +43.124,0.996,0.442,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcYUAFUHQMzlGZHE2dCE4fRLDhoDjQ+Nw5xGSitDaVVobQwlGaF0uJQOgVKL4bSu6C0CpQWgNKMDKNgFIyCUTAKRsEowAAAPDAHuw== +44.120,1.004,0.459,HISTiQAAAGN42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8YUAEjDj4rTAManwVKs6PxOdDUM6OpY0WjRaC0NZRWhtICUJobTV0wlL4IpTdAaSUozYVmL7q/RsEoGAWjYBSMghENAFMcB78= +45.124,0.999,0.459,HISTiQAAAGN42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTDcZsANGmAI0mhlNnhVKs0FpFjT1rDhoFjS+GJTWgdKKUJofzXwYPwJK74fSl6G0KpTmQbOHkWEUjIJRMApGwSgYBXAAAN73B7E= +46.123,1.001,0.442,HISTiQAAAGh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMzGxQBlSegQNKs0JpdijNB6W5oDQbGs2Bph5Gs8AsQqNZUNUxhkH5hVBaFs0cJjT75KG0C5S2Q3MHzHxGhlEwCkbBKBgFo2AUYAAAlrEGDQ== +47.124,1.409,1233.125,HISTiQAAAMt42u2asQ3CMBREzzZG0DEABSMwBSU1FdNQ0LMAYzAeEpiCL1kCR8YmeddcfvL/9925zfp0OUvyemC/kcJue7s+S8VjepgldnqHN6xMX8jsCWbezsXEh8SrTJ+tQV2QN/cMuGfAPZMHfvGN33/Q5/DNufhudo5r7A/d6Cav4fOucE+r7+giT3QP19VrjU7yQ2c9nb2w71QXOslxzPr8lxwK5369d2r78M2+En79txNNneOp9eGbvjH7WSZeGP70fa2+uQAA1XAH+BcJvg== +48.533,0.591,0.311,HISTiQAAAFR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMjNegDKg8AxeUZkGjWdFoTijNjqaODY2GqWdEU8eMqo5RHMpPRnMHTN8oGAWjYBSMglEwCqgIAEz/Bas= +49.124,1.000,0.442,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTH+hDHYozQGlWaA0M5RmhdJsaDQ7Gp8Djc+Iwzw09YzcUH41lJZBs5cXzXxlKO0DpdOgNDeau6D+HgWjYBSMglEwCkYBMgAA774G+g== +50.124,1.000,0.442,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTLcZUAETGp8ZSjNCaRYozYomz46mH5c+dhzmy0JpbSitBqX5oTQHlOaD0n5QeibUGph75ND0oftnFIyCUTAKRsEoGAVAAADL1Aa2 +51.124,1.000,0.442,HISTiQAAAGN42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHcZsANWKM2CxofR7FCaEYc6qH0MbGg0TJwTzRwY3xJKi0FpPijNgcaPhdIbofQjKK0EpbnQ3DcKRsEoGAWjYBSMAiQAACOuB7k= +52.124,1.000,0.475,HISTiQAAAGd42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8YUAFUHQMrlGZD46PTHFCaB0ozo+lDp5nQ1PFDaVEobYDGh5nLAqUFoXQalJ4DpZ9DaXEozYlmDyMaPQpGwSgYBaNgFIxoAAB6gAfD +53.124,0.999,0.442,HISTiQAAAF542pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTO+gDHY0mhNK8+AQZ0UTZ8GhjhOH+TD9vFDaEUp7QGllKM2BZr4Mmrq5UHohmnkwfcwMo2AUjIJRMApGwSjAAABYjwfb +54.123,1.001,0.442,HISTiQAAAGt42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTJ+gDGYozQuluaE0B5RmgdJcUJodSvOgibOg0cxo6tnRxIWgtCuUjoTS0lCaD0pzoqk3h9IdUHorlBZHczcjwygYBaNgFIyCUTAKMAAAl8cH4w== +55.124,0.999,0.442,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI+gDEYozQylWaA0K5o41BwGNjRxbjQ+Kw5z0M3jhdJqUNocSkui0TB1IlDaD0pvhdLHobQglOZC89coGAWjYBSMglEwCpAAAH2wB8M= +56.123,1.001,0.442,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTB9hDCjNCaU5oDQXDj4blObFoY4VBw2TZ4fSQlDaF0qbQGllKM2P5i5JKO0KpSdA6eVo5sHUMzKMglEwCkbBKBgFowADAACIDQfh +57.124,0.999,0.459,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTA8YUAEblGaE0swwhVCaA0qzQGl2KM2Kpg5dPTsazYKmThFKm0JpFSjNi2aOIJQOhdLLofQlKC2Npo4RjR4Fo2AUjIJRMApGARAAAFlsB78= +58.123,1.000,0.442,HISTiQAAAGB42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHcZsAMmHDQLlGaE0hxQmg1Ks6PxWXHoZ0FTJwWlTaC0OpTmQzMHZl8glF4OpX+jqRdAc+coGAWjYBSMglEwCpAAACNmB7k= +59.123,1.001,0.442,HISTiQAAAF942pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTE9gDCjNgkZzoMmzQml2NHVsaHxmKM2Ipp4Zhz5dKO2KxudDs18QSjtD6alQ+gGUFsOhbxSMglEwCkbBKBgFSAAAnxwHxw== +60.124,0.999,0.492,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTLcZUAEjGs0GpVnQ+MxoNEyeFUqzo5nDiaafDU2dBJRWhdLSUJoDTT8flPaB0rOh9A8orQSlZXD4Cxd/FIyCUTAKRsEoGFEAAAFqB7U= +61.123,0.999,0.442,HISTiQAAAGB42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTDcYUAFUHQMjGh+XODMazQKl2QioY0VTLwqlDaG0MJTmRzNPEEqHQeldUPoLlJaF0lxo9o2CUTAKRsEoGAWjAAkAAMvVB68= +62.122,1.000,18.481,HISTiQAAAIp42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTI8ZsAMmHDQrlOaC0mxQmhlKc6Cpg9EsaOpg4pxQWgpKW0FpSSgthKZOFEo7Q2mYR95CaQEozYPDP4wMo2Akg9H4H43nUTAaz6NgNF5G/Tvq71H/Di5/jto76u9Rewev/YxDPBxGwSgYBUgAAP6WB8U= +63.122,1.001,0.459,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTM8ZUAErTAJKs0Fpdhx8VjQ+TB8LGp8Vhz6YeQlQ2hxKS0BpHijNBaX5obQ7lF4GpbdBaRk0cxnR6FEwCkbBKBgFo2AUAAEA1joHzQ== +64.123,1.000,0.442,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPehDEaYAAMqYEGTZ4fSHGjqOdDUw2hWND4zGs0GpWWhtBEaXwBK80FpISgdA6VXQ+mLaPp40OwdBaNgFIyCUTAKRgESAABF0ge9 +65.123,0.999,0.442,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcYUAEjlGaB0swE+Gxo4mxo8qxofA40cRgtC6UtoLQ8lOaF0lxQmhtKu0PpxVD6C5SWhNL8aP4ZBaNgFIyCUTAKRgESAAA10Ae7 +66.122,1.001,0.459,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTM+hDFYozYxGw8TZoDQHmjg3mjyMZoTSLGjqYXx2NPNUoLQrlNaA0nxQmhNKC0Fpfyi9BErvgdLiaPoY0ehRMApGwSgYBaNgFAABANPaB80= +67.123,0.999,0.442,HISTiQAAAGF42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPdhDDSaHUqzoPFZoTQnlGaD0hw4zGFG04euHsaXg9KaUJoPSvOgqReF0hFQej2UfoBmDrp7RsEoGAWjYBSMglGABABLoge9 +68.122,1.001,0.475,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHcZsANGNBqqn4ENSrNCaWY0cRYCfG4ozQGl2aG0IJTWgdJiaPKcUJoPSsdC6R1Q+g2UlkWzDx0wMYyCUTAKRsEoGAWjgAEAIHYHuQ== +69.123,0.999,0.459,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTLcYiAOMUJoNSjOj8dnQ1LNCaXYozYmmjwVKc0BpAShtC6WFoDQ3mnoYPwFK74LSd6C0LJQWhHkQzf2jYBSMglEwCkbBKAACAO+JB7M= +70.122,1.001,0.442,HISTiQAAAGN42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTDcZsANGKM0CpZnRaFY0PhManw1NnAnNPFY0cUUobQSleaA0N5p7OKG0PZTuhkrbQvnSUFoEzfxRMApGwSgYBaNgFCABAKc4BrI= +71.123,0.999,0.426,HISTiQAAAFh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTHehDBaYAAGaBQfNjEazofFZcfBhtDqUtofSEmjmwOwXgdKBULofQjGiy8PoUTAKRsEoGAWjYBRgAQDzCAa6 +72.122,1.001,0.442,HISTiQAAAF142pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTNcZsANGHHwWKM2MRrPgoJnQaBYc+vigtAaUFoDSgmjqeKB0IpReBHWeDJQPo3lx+GMUjIJRMApGwSgYBUAAAIOkBq4= +73.123,1.001,0.475,HISTiQAAAGB42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTNcYsAOoegZGND4LGp8BTR0zGs2Kph6XuSJQ2hBKC0FpPjRzeKC0D5RuhhpnD+XLoulnweHOUTAKRsEoGAWjYEQDAHGaBqw= +74.124,0.999,0.442,HISTiQAAAGJ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTLcYsANGKM0MU4hGs0BpVjQ+G5o6mH52NHlWNHl5KG0JpYWgNBeaOhg/HErPgtJ/obQMmn4OhlEwCkbBKBgFo2AUYAAA7wEHsw== +75.123,1.000,0.459,HISTiQAAAGh42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTLehDGaYAJRmZEAFTGjq0NXDaBY0GqaOC0rzoIlzQGk1KK0DpQWgNBuUZkfTHwylm6HOlYPypdH0M6K5bxSMglEwCkbBKBgFQAAAzUwGtg== +76.123,1.000,0.475,HISTiQAAAGR42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTDcZUAFUHQMjGp8FBw1Tx4YmzoYmz4zDPJi8GJQ2htLCUFoQzTwRKO0BpeugxuhC+bJQWgjNPgY0+0bBKBgFo2AUjIIRDQCpiAay +77.123,1.001,0.459,HISTiQAAAGZ42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPcZsAOoegZmNBpdnBVKc0FpFjR5DjR9LGg0TFwbSptCaRkoLYBmviCUDobSG6D0BSitAKW50cxnZBgFo2AUjIJRMApGARwAAET6B70= +78.124,0.999,0.475,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTFcZsANGNBqqn4EFSrOh8WHqWNHUw2hmHPIwfZxQWgdK80FpDjSaF0p7Q+lpUGPsoXxRKC2Mw32MDKNgFIyCUTAKRsEoYAAAYlAGqg== +79.123,0.999,0.459,HISTiQAAAGV42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTPdgDCjNAqUZ0WiYPDOUZkMTZ8VhDisOdTB5TiitB6U9obQIlOZC0ycApV2gdC/UmTB3SqPpY0bzxygYBaNgFIyCUTAKgAAABCkGvA== +80.122,1.001,0.459,HISTiQAAAGN42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTNcY8ANGKM2Mxoeax8ACpVlxqGNDE0enYeaIQ2lVKC2Aph9Gc0DpWCg9HWqdDpQvCaVF0cxnZBgFo2AUjIJRMApGARwAAHIqBqw= +81.123,0.999,0.557,HISTiQAAAGl42pNpmdzBwMDAxAAGfgoMDMxuBjsWQLgMTFcZiANQ/QxsUJoRSrNAaWY0PjsaH109K5q5glBaHUrzQ2leNPUcUNobSndDjXeG8sWhtDCafQxo7iAEiFU3CkbBKBgFo2AUDEkAAGNwBqo= diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.6.logV1.hlog b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.6.logV1.hlog new file mode 100644 index 0000000000..3356576fbe --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.6.logV1.hlog @@ -0,0 +1,92 @@ +#[Logged with jHiccup version 2.0.6] +#[Histogram log format version 1.1] +#[StartTime: 1438867590.285 (seconds since epoch), Thu Aug 06 06:26:30 PDT 2015] +"StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" +0.133,1.005,2.802,HISTIgAAAFd42pNpmazIwMAYxgABTBDKT4GBgdnNYMcCBvsPUBkeBkYGZqA8MwMbAzsDC5DFBCTZgJCDQY1BjkGLQZRBlUEPCB8zWDCYMxgDZZkZhgJgHDibAY8JB/A= +1.138,0.998,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDDDACMQsQJKRgYOBlYEZCFmAkB3IB/HkGJQYJBi4gXIyDPGMTAxzGKQZ2EC6AJ7YBtg= +2.136,1.001,0.475,HISTIgAAAEt42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDDDACIfMYMjCwArEzEA9TEBSgkGJQZCBn4GLQYDBh+ESw2cGYSBkYWAEAKZvB9Q= +3.137,1.001,0.492,HISTIgAAAE542pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPUBlGsCwTkGYFYg4GFgZmIIsFDNmBcrIMGgz8DDxAMTGGNIZHDPsZpIFskHlMALndB9o= +4.138,0.999,0.492,HISTIgAAAE142pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPUBkWBjY4BpGsQMwOFGcG6mEBQl8GOwZRBj6gGBtDBMMOhpUMUgxcQDkmBkYAwSAH4w== +5.137,1.003,0.459,HISTIgAAAEx42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPcBmQHBuYZmZgZWABYnYgZmNgBLI5GXQYrBj4wSKiDB4MexgeMwgw8DKwAgCbcgfb +6.140,0.998,0.492,HISTIgAAAE542pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDMiAGaiClYGRgR3MYmdgA5JsQDYLgxKDBgM/kGYBkq4MFxg+MEgyCAFVAs0EALiCB9c= +7.138,1.001,0.475,HISTIgAAAE942pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDDDACMQsDMxAyAaE7ECaFcxnAdLMDMoMagxCDBxAcTGGIIZbDBcZRBm4geYxAQCqKAfZ +8.139,0.997,0.459,HISTIgAAAFB42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDDDACMTMQBXsDCxANguQzcLABoQsQMjKIMegzsADFGNmkGBIYGRmWMIgw8DLwAQAj9EG1Q== +9.136,1.004,0.475,HISTIgAAAEx42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDMiACYyZGRiBEESzACEzkMXCoMmgzCDAwM/AysDH4MXwhOE5gwiDIFA1IwCmuAfX +10.140,0.996,0.459,HISTIgAAAE142pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPUBk2IGYBQiYGZgZWIGQBk0wM7AyMQDYbgy6DOQMHAzeQJcTgzHAFCMWBfGYAm8UH2A== +11.136,1.233,1035.993,HISTIgAAAJx42pNpmazIwMD5jgECmCCUnwIDA7ObwY4FDPYfGFABRAUjGDNCWcxgzMnAChZjYeBn0GbQY2ADsoYCYBy1eRCYyjiA/mGkqnpGiuxiHBI2MxLQy0g12cFiMyNJNP1V0d5MRhTMiCGCC1Nb5cDYjB0y4ZShvUr6msgEhsxQGhUyU1lsIHXjMpEFClmxsLCLks4aHOawMDACAO56ClU= +12.369,0.771,0.459,HISTIgAAAEx42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPSDJMDCxAyMTADMSMQMwGJFmBIiC2NIM8gxADO1BMjMGdYT5DL4M4Ax8DEwCR7Acv +13.140,0.996,0.459,HISTIgAAAEp42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDAjACJRnYmBmYAHTrEAWI5hkAdKSDKoMXEDIxiDI4MlwnJGDQQLIYgIAjsYG0A== +14.136,1.001,0.459,HISTIgAAAE142pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDAjAyMACxMxAVSxAyAqk2YAYJMLAIMmgwSDIwAkUFWXwYzjL8JNBnEGAgREAlsUH1A== +15.137,1.002,0.459,HISTIgAAAEx42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPcBlGBmYGViCEkExAmomBBYp5GYwYdBkkGfgY2BiEGFwZLjJcYBBm4GJgBACcBQfc +16.139,0.998,0.475,HISTIgAAAEt42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDMiAEQiZGViBkAUIWcE62IEsZgYpBm0GHiCbk0GQwYfhAcMjBjEgC6geAKdjB9Q= +17.137,1.003,0.475,HISTIgAAAEl42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDMiACQxZGRgZ2ME0M5BkBrNUGLQZBICizAzcDL4M1xneMEgz8AFlGAGnqwfY +18.140,0.998,0.442,HISTIgAAAE142pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPDAxIcswMjAwsYMjAwAaErEBRFqAYI4MDgzKDKAMfgziDHIM5w0WGkwxiDLwAiiQH1Q== +19.138,1.000,0.459,HISTIgAAAE542pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDAjABITMDIwMXECanYGVgYWBDcxiBpKKDCoMQgzcQBEBBh+GjwynGCQZOBiYAJl6B9c= +20.138,1.002,0.557,HISTIgAAAFF42pNpmazIwMDgwgABTBDKT4GBgdnNYMcCBvsPUBlWBhagLAiyACEzWC0jkMUBxIwMbAyaDJYMfAzsQB4/gxvDM4ZTDNIMPEAZBGAEAP1VB+Y= +21.140,0.998,0.459,HISTIgAAAEt42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDDDACIRAYTBkY2AFqmVjYAFiBiCbmUGQQROIuYGiggwejLwMuxhkgCxGAI58BtQ= +22.138,1.000,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDDDACJRlZGBlYIaqYmdgA0IWoBgjUEyGQZlBCMhnYhBlCGOUYFjLIMnACdIBAJ0WBtg= +23.138,0.998,0.492,HISTIgAAAEx42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDDDADJYFkSxADKLZgJgJyldmUGEQYOBhYGXgYwhlZGTYxyDMwM/ACNTBCACrbwbX +24.136,1.003,0.573,HISTIgAAAE942pNpmazIwMDgxgABTBDKT4GBgdnNYMcCBvsPDDDACITMQBXMDBxAkgUIQWxmMIuFQYFBkYGPgQ0oJsUQwsjMsJ1BkkEAZh7MBAD0rQbn +25.139,1.000,0.459,HISTIgAAAEx42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDDDACCbZGFiBkAOokpGBBchiY2AGQxkGTQZBoAgbAy+DKyM7wykgT4CBEQCPpwbX +26.139,0.997,0.475,HISTIgAAAEx42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPUBlGIGQFyjMDSVYGFjBkgoqyALEigzqDMAMnAy+DGIMfoyTDNAYhBh6QCgCfhQbZ +27.136,1.004,0.442,HISTIgAAAEp42pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPDDDAyMAMxixQdSxANgMDGxAyA6EigyoDJwM3AzsDH4M3w3eGGwyyDDwAhpMH1A== +28.140,0.996,0.459,HISTIgAAAEt42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPUBkWoBwIMgNZ7ECaHcxiYWAEk1wMBgxmDGIM3EC+IIMLw2eGTQz8QFEmAJxuB9k= +29.136,1.000,0.492,HISTIgAAAEp42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDKiAEQhZGNiAmBkIGRlYwXwWBmEGZQZ+MEuQIYWRi2EbgwoDB1AOqAMAqa0G1w== +30.136,1.003,0.459,HISTIgAAAE542pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDDDACIQgwMzAxsAChKxAmhlIMwJZzAxSDEoMvAzsQCjE4MPwnuE+gwQDPwMjAJclB9Y= +31.139,0.998,0.459,HISTIgAAAFF42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPUBkuBmagLBMDCwMbkGRkYAVCFiBmAGJmBj4GRQZdBlmgKjYg6cDwk2E+gwQDDwMzAJzYB9s= +32.137,0.999,0.459,HISTIgAAAEp42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPSDKMYMzCwM7AysAMFGEC0iAWCwM3gwyDBgM/mCfIEMfIzrAeSPMzMAIAj9AG1g== +33.136,1.004,0.459,HISTIgAAAE142pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDDDACJbnAGJWMGRiYAZCJgYWIGRmkGNQZRAAyjIzcDKEMrIy7GGQYRBkYAQAj3YG2g== +34.140,0.999,0.442,HISTIgAAAEl42pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPDDDACITMQMwGh6wMLEDIBBRjZZBiUGLgYeAAsngZ3BleMTxmkGHgBQCIIAfT +35.139,1.001,0.459,HISTIgAAAE142pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPcBkWIGZiYAZCCJuZgY2BFYhZgCQXgwmDNgM/AzuQJ8bgzHCX4TyDCAM3AyMAm0EH2g== +36.140,0.996,0.459,HISTIgAAAEp42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDMiAkYEFjNmAKpmhkBUImRjEGOQZ+BnYGTgYJBhCGD4zXGMQZeBhYAQAlpQH0Q== +37.136,1.004,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPUBlGIGRgYAWqYGZgA5IsQJoFzAPxmRkUGYwZhIAsVgYZBmeG3wwHGCQYOEC6AKnrB9w= +38.140,0.996,0.492,HISTIgAAAE542pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDAjAyMAMhEwM7EDMCoZsQDEOoBgLgzyDIgMPkM/KIMJgxfCI4SODOAMv2DxGALigB9c= +39.136,1.000,0.459,HISTIgAAAEt42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDAjAysAIVMECpFmANBsDB5AG8ZiAWI5BhUEKLCbBEMzIyTCPQYyBl4EJAJEUBtg= +40.136,1.000,0.492,HISTIgAAAE942pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPUBkWIGYGQkYgZgGqY2JgB9LMYMgGhIYMDgyyDBwMrAz8DCEMzxlmMkgDWSDACAC9Gwfg +41.136,1.002,0.492,HISTIgAAAE542pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPcBkWIMkMJFmBkJmBF4hBLBYGTqAcO4Migw+DAAMHUFSMwYjhHMNNII8frJMRAL4nB+I= +42.138,0.998,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDAjAyMAMxKxAyMTAAmSzAVnMQMgC5EszaDFwMXAAefwMngyvGZ4wiDIIgPQAAKfOB9U= +43.136,1.002,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDAxIciwMzAxsQAgimYA8RiDJAeSxMygwKDEIMHAC2YIMvgwvGC4zSDHwA+UZAalrB9o= +44.138,1.002,0.492,HISTIgAAAEx42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDAjACMTMQMgKhCCaBcxiYmADyigyKDFwM7AzcDGIMzgzCjBsBdICQFmgLgCrjQbb +45.140,0.996,0.459,HISTIgAAAEt42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPUBkWBkYwZGFgBaoCYTYgyQ7EbEAxNgZjBiMGfjBfjMGB4QPDLgYhII8JAJv1B9g= +46.136,1.003,0.492,HISTIgAAAFF42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPcBlGIGZhYGVgBtLsDBxAmh3MYwHKsDMYMOgySDBwAfmiQNOOMDxiEGIQBsowMDACALywB+E= +47.139,1.001,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDMiAkYGZgQWImYCYmYEViBmBJBNQTJxBg0GEgZOBjYGPIYjhGsNroIgwUIYRAKbXB9Y= +48.140,1.000,0.442,HISTIgAAAEt42pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPDAjAxMDIwAzG7AysDFxAkpmBBUwyMtgwGDIIM/AycDCIMpgz3GI4CaR5AItmB9k= +49.140,1.000,0.459,HISTIgAAAE142pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDMiAiYERCNkZmIGQlYENyGdmYAGKszCIMcgwcAMhFwM/QzjDK4anQBF+BkYAli4H0w== +50.140,2.452,1895.825,HISTIgAAAKx42pNpmazIwMB1hAECmCCUnwIDA7ObwY4FDPYfGGCAEYiZwWoYgZgZzGcCQwYGVgZhBhEGbgZ2BjYgW4+hjqGcgR/IYgSrohxQx5RRm+lpM+OA6aaXzYwDJDtQNjPiVUmKLC1V08pmRqJoRhqoHhw2E8aMA6aS9jYThkxEqSJe3eAykQkJMqPwSBMbSN2UmMjKwAKHrFRhDS1zOBk4gJATijmw8ge5GgDvng6P +52.592,0.546,0.442,HISTIgAAAEl42pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPUBlmoBwLEDMBWcxgkhEqxgpksTNIMQgxcDGwAfl8DDIM1Qy5DLwMrAB+SwaO +53.138,1.002,0.475,HISTIgAAAE142pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPcBlGBmYgZGRgAWImIMkMFmEDslgYOBnMGEwYJBjYGbgZeBkSGG4xrAXzgGoAq7YH3Q== +54.140,1.000,0.524,HISTIgAAAE942pNpmazIwMDgwAABTBDKT4GBgdnNYMcCBvsPDAjACIQgNWxAzMLACsTMQMwEZDEzKDLIMwgycANZ4gz+DI8Y7jBIM/AA5RghOgHYtgfd +55.140,0.996,0.475,HISTIgAAAEh42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDMiAGaiCBQwZwZgZCNnAumQY1Bh4gDwWBkEGZ4bLjMwMkgxCQBlGAJ0MBtI= +56.136,1.000,0.492,HISTIgAAAE942pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPUBkWoBwjEINIZgZWMIuVgQOIWRnYgLKaDPYMXGBRDgYvhhsMVxhkGASB+hgZGAG66gfd +57.136,1.003,0.492,HISTIgAAAFF42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDDDACIQMDCxAkomBFUizMLAxMANFmIF8NgZFBnUGYQYuoCgfgzfDR4brQB4/2DwmALhmB9s= +58.139,0.997,0.475,HISTIgAAAEt42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPUBlGqDwjAwsQsgFpNgZmKASJyDMoMAgx8AJV8DA4M9xheMsgziAClGMEAKc6B9M= +59.136,1.003,0.492,HISTIgAAAE542pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDAjACITMUMjIwAJUzczACsYsDHIMqgz8DNwM7AyCDN4MVxneM8gz8ABVAXUBALeqB9s= +60.139,0.999,0.459,HISTIgAAAE542pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPUBlmoBwLAyOQBrFAkBmMGYFyrAxcDGYMOgwiDAJANbwMrgznGB4ziDJwMjACAJn0B9c= +61.138,1.001,0.492,HISTIgAAAE942pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDDDACJRlYmBmYGVgY2AB8piBkAuIWYCiLAySDM4MQmAZMQZ/hg8MuxhEGHgg+gC7NQfe +62.139,0.999,0.475,HISTIgAAAEt42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDAgAkmUHklwMrAwsQMwMZINYTEAozaDEIAxkMzPIMKQy8jDMA/J4geKMAJ7UBto= +63.138,1.001,13.959,HISTIgAAAGJ42pNpmazIwMC8igECmCCUnwJQzM1gxwIG+w8MDHA5ZiBmZeBgYASS7AwsYD6IzQakVRjUGEQZuICyYgw2DDcY7gLZwkAVIMDIMNQA46jNA2YW45CwmZEikxhpJwsAR+UIUA== +64.139,0.997,0.492,HISTIgAAAE942pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDMiAhYGNgRUIWRiYGdiBmAUI2cGitgx6DJJAmp2BhyGU4SrDbgYBBg6gCkYGRgC9Qwfe +65.136,1.002,0.459,HISTIgAAAE142pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPcBkWBmYGNgZWMGQB8kFsJgZ2BkYg5mXQYNBnEGbgArL5GNwYXjAcANJ8DIwAnS0H3g== +66.138,0.998,0.459,HISTIgAAAEt42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDAjACIasDOxAkgWoloWBGQiZwGwFBjUGHgYOoBw3gx/DBYbfDMIMQgzMAJalB9I= +67.136,1.002,0.492,HISTIgAAAFF42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDDDAApRlYeBgYAOymYEkKxAyglnsQBjLYMEgwMDNwAkk7RiOMexi4GfgAsozMDACAMCHB+Y= +68.138,1.002,0.459,HISTIgAAAEp42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDDDAzMAKVgGiWYAQRHIAITOYJ89gySDMwAMUFWJwYtjD8JJBioGPgRkAmrYH2w== +69.140,1.212,763.363,HISTIgAAAKR42pNpmazIwMCZwwABTBDKT4GBgdnNYMcCBvsPDMiAEQiZgJgFiFnBqpmg+vgY+IEizGBxFYZshjQGbgZ2II8WgDamjtpMG5sZB8xu2tvMSJE87WRpYzYjSXxqqh4cNjOi0IxYRXHR9FdFfTMZoSQhTJyqgVVJqnnEQSYqq6OFSvJMZEKBzGh8XGLEq6SPieTrZgG2a1igED+LeJXYWYwAsfYJeg== +70.352,0.784,0.475,HISTIgAAAFJ42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPUBkuBm4GTgYWBl4gZmFgY+AAqmUEYhYGdiDNw2DGYAMk+YGQl0GLoZ6hDUizAfUxAgCtOgdN +71.136,1.001,0.492,HISTIgAAAEt42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDAjACMaMDKxAzMzADibZgCQLUESFQQ1I8gBFuBhCGLYzCjMIMYiAVTMBAKvBBtk= +72.137,1.003,0.475,HISTIgAAAEt42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDAjAyMAMxEApBhagSiYgyQjEELYsgzoDLwM7kMfFEMBwlpGLQYaBB6QHAJ0DBtc= +73.140,0.996,0.475,HISTIgAAAE942pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDDDACMTMQJKJgR1IsgLZIB4zAwsQszKIMWgyiANpVgZehgiGMwz3GeQZRIEyTACm5QfS +74.136,1.004,0.459,HISTIgAAAEx42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDAjACJZnZmBlYAFCJiBmBLKZgSQLgyiDJgM3AzsQ8jEkMjxkeAYU4WdgAgCWiAfX +75.140,0.997,0.492,HISTIgAAAEx42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDDDACJZnBkIWMIsNiFmBoiARJgZJBmUGYSCfnYGXoYCRiWEngwiQDdLDCACq1wbX +76.137,1.000,0.442,HISTIgAAAE142pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPDDDACJZnYmBmYANiJiCfmYEFCFmBLE4GeQYFBn4gzcIgxuDNcIzhB4M0gwgAho8H0g== +77.137,1.003,0.459,HISTIgAAAE942pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPDMiAkYGZgRWoioWBE8hiZmADirABeawMUgxaDHwMHECeAIMfw2WGrwwiDFwMzACYHQfY +78.140,1.000,0.459,HISTIgAAAFB42pNpmazIwMBgwQABTBDKT4GBgdnNYMcCBvsPUBlmIGYEyjMDISsDCwMbkAbx2IBsRgYOBj8GDwZJBnYgX4TBlmEdw2kGUQZ+BkYAnacH3g== +79.140,0.998,0.475,HISTIgAAAE142pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDAjACJRnApLsDMxgkoGBBchiA2JmBhEGHQZBoBgbAz+DL8Npht8M4gwCINUApvkH1A== +80.138,1.002,0.508,HISTIgAAAE542pNpmazIwMBgxwABTBDKT4GBgdnNYMcCBvsPDDDACIQsDMxAzMTACqSZgBgE2RjYgaQUgxaDIFhMnCGY4Q7DXQYJBn4gD6wTAMlCB90= +81.140,0.997,0.557,HISTIgAAAFB42pNpmazIwMDgwgABTBDKT4GBgdnNYMcCBvsPUBlWoBwjAzOQZAFCZiCbhYENyGOFimkz2DPwM/AB+dwMTgzHGJ4zCDDwgNXBACMA/WwH4w== +82.137,0.999,0.475,HISTIgAAAE542pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDDDACMZMQMjCwArEzAxsDJxgEWYgKc+gzsDHwA0UF2AIYrjK8IJBnIEfKMcIAKfXB9Y= +83.136,1.003,0.475,HISTIgAAAEx42pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPDAxwOUYgycrADIQscMwGFAexpBgUGbgZuBg4GAQZYhluMTxhEGYQAupgBACn3gfZ +84.139,1.001,0.492,HISTIgAAAEx42pNpmazIwMBgwwABTBDKT4GBgdnNYMcCBvsPDDDACIWsDMwMbGCVzGAeA5DHzCDBoMsgzMAB5MszeDI8ZLjCIMfAC1bBCAC31AfZ +85.140,0.996,0.442,HISTIgAAAEt42pNpmazIwMBgxgABTBDKT4GBgdnNYMcCBvsPUBl2IGQByrMCSTYGDiBkBfNYwSK8DG0M7gzCDJxAOX4GC4ZFDLMZRBg4AZVqB+U= +86.136,1.001,0.557,HISTIgAAAFF42pNpmazIwMDgwgABTBDKT4GBgdnNYMcCBvsPDAjACISsDCxAVSCSDUiD2MxAUTYGcQZNBiEgn5VBgiGU4Q7DYwYxBm6gDAMYg3UDAPppB+I= +87.137,0.999,0.541,HISTIgAAAE142pNpmazIwMDgxAABTBDKT4GBgdnNYMcCBvsPDAjACITMQDUsQJIVTLIAaRDJwiDMoAnEnAwcDCIMVgxXGP4ySDHwwMwD6QUA6PAH3Q== +88.136,1.002,0.475,HISTIgAAAE142pNpmazIwMBgxQABTBDKT4GBgdnNYMcCBvsPUBlGIGQCYxYGVgZ2IMkGxIwMzEAaREowaDLwM3AAebYM6ozdDE4MIkBVQF0An+EG3w== diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.7S.logV2.hlog b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.7S.logV2.hlog new file mode 100644 index 0000000000..f9e801f9d5 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/jHiccup-2.0.7S.logV2.hlog @@ -0,0 +1,66 @@ +#[Logged with jHiccup version 2.0.7-SNAPSHOT] +#[Histogram log format version 1.2] +#[StartTime: 1441812279.474 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015] +"StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" +0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= +1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= +2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= +3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= +4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= +5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== +6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== +7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= +8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw +9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= +10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR +11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== +12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko +13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= +14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ +15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== +16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne +17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 +18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== +19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== +20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== +21.135,0.999,0.492,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhk1AjINDRECAj4+Hi49LKS5CS2EGo1kXa4ANExMDEwAmOQil +22.134,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBFmHhE+MRExCTEZAS4RMQERvRI1hSuMTidY3KQ4mAAXhgks +23.131,1.004,0.508,HISTFAAAAEB42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNMhotHSEBASEyMg09MQUSIT6tKS2YKY8gfFj8tJmYmJgAsowkz +24.135,0.998,0.492,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEBLjkhETEpET4BISEhCR6FsqAQFY1jjBoTWPQEOJiZAC2aCUY= +25.133,1.002,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBkuHh4BITEpMSEpLiE5AS6FoAgdpQuMJk9YzMRYmAAdngk2 +26.135,0.998,0.508,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUAkOKSEJKTUJOT4+IQkeIT69LYwVCnIbGI0eMZtJsTAxMwEAQvkJyg== +27.133,0.998,0.442,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBE2CQUZFTkZOSURKQkRMT6NKYwhbYxaOocY/a4xSUmwAQA4pQpb +28.131,1.002,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGtFDcHIy0jDQUdPjENFZUzjNNYHCT4uBQkzJiYADIGCcY= +29.133,1.460,968.884,HISTFAAAAJZ42pNpmSzMwMDUwgABTBDKT4GBgdnNYMcCBvsPEBE5AwMDJSUFISk2ETYuAS6PQ0xSXCzsTEw7GZnKgdCTyZLJGog1maSZZIFYGkpLMnEz8QIhOolgcTKxAiEzmGRFYxMShbEYUCAalzRBsjSjARYmTIBNjDKFSIIsIMDGAgPYWJRJE1DIxQEEaAQHF2GCNDVsAE2dFJE= +30.593,0.541,0.459,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEFCxUNBRkFMTE+Pj4ZHgGHFYwGIkJcMiIpbEwMTAAdQQhJ +31.134,0.997,0.737,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEJGAHsYexqKaIAcPPRMVKTEhoR6mJUxqfBx8LFwCTOxM0kwAfR8KqA== +32.131,1.002,0.508,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEJKCDMcHJw8jOTUfNSEZGQuUb4x9GHxkJDg2hMA4WViYmAHWrC2k= +33.133,1.000,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBGXGK8QHS09PRM9BRMxBa55jBOY03REhByE3DhYADicCkc= +34.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBE1NzsfJwMVEw0pFS0hOZm4FqYKPy2FAoUJjFIsTAA/mQql +35.131,1.000,0.459,HISTFAAAAEN42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBERMy0jPTk5LRUFJQk1GamYdUzHGO0UxIrUljBKsbEwAQBKXgqU +36.131,1.001,0.557,HISTFAAAAEd42pNpmSzMwMCgygABTBDKT4GBgdnNYMcCBvsPEBExJzcNMyU5PRUpLSkJKYWwHqYWRjslkTKNC4wKHGwMTExArUwAi/IKnA== +37.132,1.002,0.442,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPEBEFLRsVPQkTKTkhPT4ZBTm3V4yTGD20pFoYtZqYxESYAEjICok= +38.134,1.000,0.803,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNERM7Hwk3LRslMSkZMQExDLGQL0yTGIC2pKJ1VjCwcTJpMAFufCso= +39.134,0.997,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE5Oz8DPRsFORM5FQkNKaGCA8wtjCoSfBYSTYxCLEBtTABiWgor +40.131,1.000,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBExJQUNFTElFRUZBRUZDTGfJqYKHzmhHka5ZUwSQmwANK0J+g== +41.131,1.002,0.475,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBE2Hj45PiEFGSU5EQkpKREJuVmMLYwaWk8YQyYwa3CxMTABAEOgCdQ= +42.133,1.000,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBk+Lg4+ER4hMT4hIT4lLh69OAOZZ4wOr1hCpFiYABjUCSY= +43.133,1.002,0.442,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBFmLgEJMTERHjEuCRERBSERoww5rRuMendYPFRYAA3tCTM= +44.135,0.998,0.590,HISTFAAAAEJ42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBk+FT0lJTktJSUjOTE1OQGpmnOMdnorGF3WMemxCTIBAEAhCnU= +45.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWMS0DIyMFOSsNPTEFMSGNA4x+LxidfOp0VjBKcLAAAECLCv4= +46.131,1.004,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEuMS0VEyMlLSkzGQUJOSkJj6RnjE56WxjNWpik2JgAO34KfQ== +47.135,0.996,0.475,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUgk2GR0ZOQkSAR4aLS0KKTyNtDqOWxjVGu2fMGlJMTEwANsIJvA== +48.131,1.950,1803.551,HISTFAAAAKF42pNpmSzMwMD0mQECmCCUnwIDA7ObwY4FDPYfICKsTExMLCysLCxsbEwMTAIsDHIsWTwsbNsZmcqZKpncmayZLIFYnUmWSRoMIbQkEy8TNxQjkwgWJxMrGDJDaews/KIMKBCNSytBZCYqYGHCBNjEiBckoJAFBNhYYADBwipIhkIC0lwcQIBGcHARJqigBkwKCQgICSAIFA75IlwAeB8ZpQ== +50.081,0.050,0.393,HISTFAAAADl42pNpmSzMwMAgxgABTBDKT4GBgdnNYMcCBvsPEBE2BiYWNiYWZiYGJiZmJg4OLiYuFiYWAMWGBSM= +50.131,1.001,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2Lj4VAQkuJT45KTkOKSExI68eRgeDvB2MfcxxckwAJD8JyA== +51.132,0.999,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2NgUFGSkNAQEeJSkuKSmxhAojhZADjKuYiyS4WAAlWgm/ +52.131,1.002,0.557,HISTFAAAAER42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPUBkWPjEFGSMZKQMJJSEhPgkJiyodjZIHjB+YSvh4mBiYWJkAVc8KVw== +53.133,0.998,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBE2HjklJR0VPSUDHTUxJSkJs02MuxhtrLxKHjH6cbEAADjeCuw= +54.131,1.003,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkNPzMLIw0NLQ0pFTERCTGLT4wpQSVbGFcwynExAQA/uwsC +55.134,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPUBkWFTUjCy01BQ0VFRUJGSkJjRamiqA5jHmXGIV4ACoyCmo= +56.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNUhk1FzsrAQElFQ0xCQkJOTEDnE6ObxwrGDsYuJjUODiYASN8KbA== +57.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPMBk5FT0JAzUNKTklKQ0FMaGUJ4wJFjcYk+4wqnAwMAEAQooK6Q== +58.131,1.002,0.442,HISTFAAAAEB42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEuCRMNJwMlIzUtLR0ZMREZv6IHjFYGdUXLGE14WAA4OwsG +59.133,0.998,0.442,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBklExUdIwcdFRUlOTMZPhWXB4wBTssYsy4xKnGwAQA8bAry +60.131,1.000,0.524,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmIRcjPR0bFR0lDSk5KQkZpXlMXkF5qxh3MMqIcDIBADy8CoE= +61.131,1.000,26.083,HISTFAAAAF542pNpmSzMwMAQyAABTBDKT4GBgdnNYMcCBvsPMBkFHSMrCzEZLSUFCSkJOTmTf4xRQW2MYT8Y5diYdjIylTNVMrkzWTJZA7EmkzQYykJpSSZeJm4ghpAQFgATDg85 diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/tagged-Log.logV2.hlog b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/tagged-Log.logV2.hlog new file mode 100644 index 0000000000..520f41dc75 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/tagged-Log.logV2.hlog @@ -0,0 +1,46 @@ +#[Logged with jHiccup version 2.0.7-SNAPSHOT, manually edited to duplicate contents with Tag=A] +#[Histogram log format version 1.2] +#[StartTime: 1441812279.474 (seconds since epoch), Wed Sep 09 08:24:39 PDT 2015] +"StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" +0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= +Tag=A,0.127,1.007,2.769,HISTFAAAAEV42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPEBEJISEuATEZMQ4uASkhIR4nrxg9v2lMaxhvMekILGZkKmcCAEf2CsI= +1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= +Tag=A,1.134,0.999,0.442,HISTFAAAAEJ42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWLj45FTExAT4pBSEBKa6UkAgBi1uM7xjfMMlwMDABAC0CCjM= +2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= +Tag=A,2.133,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE+Ph4OLgk5OSkeIS4+LgEeswIDo1+MbmdYNASYAA51CSo= +3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= +Tag=A,3.134,1.001,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBExPiEpITEFGTkRKSEeOR6FkCg1hTeMXvNYlHhYABQ5CTo= +4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= +Tag=A,4.135,0.997,0.426,HISTFAAAAD942pNpmSzMwMAgwwABTBDKT4GBgdnNYMcCBvsPEBE2PiERBREpBREhER4+Hj4uvQAdrTlMBldYDDhYAAugCKk= +5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== +Tag=A,5.132,1.002,0.426,HISTFAAAAEF42pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPEBEWPhElOR4pARUpKTkpGQkxq2mMegZnGI0+MZuIcAEAHo8Jvw== +6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== +Tag=A,6.134,0.999,0.442,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIS4FITEhDiEJERE+GT6ZkhZGLbl7jEqrWHREmFgAIbAJMw== +7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= +Tag=A,7.133,0.999,0.459,HISTFAAAAEJ42pNpmSzMwMCgwAABTBDKD8hndjPYsYDB/gNEhEtMQEBBTk5MQERCRkBEQEWlh9FJbg9jE+MS5ig1LhYmADkkCcE= +8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw +Tag=A,8.132,1.000,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBEWIREgEOIQEuGT4xHg41Oo0pIqu8LYwVImwMfGBAAfkgkw +9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= +Tag=A,9.132,1.751,1551.892,HISTFAAAAJZ42pNpmSzMwMB0nQECmCCUnwIDA7ObwY4FDPYfYDJMXFxsbGwMbBwszDwsDDxsHFw6RWJMLJMZmcqBMJrJmskSiA2ZZJmkgRBCgmheIORGI1H5rEzMQAyDzFhY2EWRWUwMWCBxQtQQhAIWJiyAaEHyFbKwsLHAADYWAWmiFeKS5gACLsIEzdQICAgBIQShEfhFABXDF+M= +10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR +Tag=A,10.883,0.250,0.426,HISTFAAAAD142pNpmSzMwMAgxQABTBDKT4GBgdnNYMcCBvsPEBEeFi4mPg4WLhY2BjY2FhYOBSkpASEtoRA+NgDkCQZR +11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== +Tag=A,11.133,1.003,0.524,HISTFAAAAER42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBk2HgkZKREpEQUeGSEBAQ6xSYxhCnp7GJ02sWgJsbCwMgEAO0AJSQ== +12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko +Tag=A,12.136,0.997,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2AT4eCQURHgkuEREOHjERlSQhhWuMSV9Y7ERYWAAa4gko +13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= +Tag=A,13.133,0.998,0.459,HISTFAAAAD942pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPMBkRIR4RMRk5KQE+PgEhMRmzEjWZJ4whW1hMBNiYAB42CTA= +14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ +Tag=A,14.131,1.000,0.492,HISTFAAAAEN42pNpmSzMwMCgyAABTBDKT4GBgdnNYMcCBvsPUBkWFhE5GT4FKQkRCR4ZCREpqwmMBhpHGG16WHx42JgYmAA6swk+ +15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== +Tag=A,15.131,1.001,0.442,HISTFAAAAD542pNpmSzMwMAgywABTBDKT4GBgdnNYMcCBvsPMBkuMTEFHgklFRkRATkJERGdKgudfYwRTSwGalwAF2IJOw== +16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne +Tag=A,16.132,1.001,0.524,HISTFAAAAEZ42pNpmSzMwMCgxAABTBDKT4GBgdnNYMcCBvsPEBE2IQEFCQkpGREpHj4hKS6NU4z7GDMkuBoYDSYw2wiwMLEyAQBQ3wne +17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 +Tag=A,17.133,0.998,0.459,HISTFAAAAEB42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPUBk2DjElIR4RHiExKQE5IT61iCodtXWMdn0sKVJMTAAekAk0 +18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== +Tag=A,18.131,1.000,0.459,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUBkWISERJSUJESklHhEJEREhqwZGLakPjDZdLBYCHCwAKOkJPg== +19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== +Tag=A,19.131,1.000,0.475,HISTFAAAAEF42pNpmSzMwMAgzwABTBDKT4GBgdnNYMcCBvsPUAk2HjkJBSk+Pi4BMT4xIQE9pxIluTOMPhtYbITY2JgAKLoJOQ== +20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== +Tag=A,20.131,1.004,0.475,HISTFAAAAEF42pNpmSzMwMAgxwABTBDKT4GBgdnNYMcCBvsPEBFmPhEJOSEhDi4+ETEeASEhswIVi1+MFjtYvCRYGJgAIP8JNw== diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/ycsb.logV1.hlog b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/ycsb.logV1.hlog new file mode 100644 index 0000000000..8d73ec9a26 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Resources/ycsb.logV1.hlog @@ -0,0 +1,606 @@ +#[Logging for: READ] +#[Histogram log format version 1.1] +#[StartTime: 1438613579.295 (seconds since epoch), Mon Aug 03 07:52:59 PDT 2015] +"StartTimestamp","Interval_Length","Interval_Max","Interval_Compressed_Histogram" +1438613579.290,0.660,0.488,HISTggAAAkp42u3dwU1jMRAA0Iw50ASdcKA2JDqgAUqkA5CAEwIFnG9nxn7vsKvVkh977Bk7/tm/d0/Pj6e4fzl9uvn6PT5+vX17e3g9AQAAAEB2cebPu8dDv/ZodygF4gDy/pA6oI4AqKOAvAcAAAAAAAAAAAAAAAAAAAAAAAAAAACA1XiuOAAAAAA/cW6EeQbyBQAAsM8H+YT5or3GGwDrBCA/9427cQCwbooPyBvtxXhjHMRV/CHPPArx005xMD4A1g0Kj7f9srzcoZ8x+fXmPYD1Cn6bT7HovMvSL3krTpgP+mscmR/3MI/0c4P+VDtHD+Mmv/lzfHw/P+f7m9/yU7/UW3HO/bnOfMd4wJx6u1seyOu9x/O/P9/OvD4m52ckn/+jrn+jDujvRvFt9iVLjfNR5xnVvgcWi41/9jobm/W3yni3YvGNzfIlkrV/VHtWf65sdOZf9vsIVZ+34bkTfe8XSeZbXPi6OOh659o1K3/jl3oSk+NaLe+q1oHeuMdB+Z/lvuqo9le5b/w973s/h8+uU9c+P4grxYdj65A4zt3fZb/Ps1q7q+yLLq3rq53HzlrPVn3+0ej5cO1ztSz3Xb7/fZuUp9X+3cLo/eeseltt3csyTyLZz2f//LPr/xPXew7WkuV9lnUxDqoHR12n932zrmOts51t0DzovU/YBl1/9n626n4+JsWn2j4/6/oUSa+XdR2PQa/rvd9QpU70xiNrHc9+DjH6e1+X7nPJ+Xm2ynlkG1xXRueN+Tr3/Xb73rP5Y5zMQ5avF+/0KAca +1438613579.950,1.000,0.511,HISTggAAA3V42u3dy3HcMAyA4QV9SBOpJWkpLWQmHaTRdODMOLl6ViuJJAh+/8G+2BLxFEBS1Ndfv38+4tuPxz/e/v+Oj59f3t+//3kAAAAAADCKePHvW+f7xmT5Zl//mV7u1lsMlic+8acYpP+4+HftoD1Gj78Nsmcv+doi+QR98mEsNt678sljUbmNv6Z+g34AAAD0cQDEJQAAANSHAAAAAAAAAJbHvCIAyJ8AxDkAAAAAAAAAAAAAAKtjnR8A5HHjBv8H+4F/1JZDvAHib3f9BXtBvABDafweANQD5CbvIvIGfQH8jH0AiDP6B3vws5R6Zwf+SX/7yhVJxxOTxh2b2Cvrc0DexRF/4CfqRnXUOvcN+p0yvhAnW9UlsZmfsLs8qm7kL+Kb/PqV+fYN+upyP/PO+rDM8qqrAPm31zjj5LhjMX1FMXmy+W8U9Rt5pIbej/atsai+s8SVcy1yPMey+Xewe0raxTpp1Xypvul7v9F+Mvv61fZNxmR/yZ5PVt3fu3tf8Kqe1Cvn4kI8Y8f6uVoe2X0f2C7r1lFUn1XrsdXWke0TonfU6Z+AEXWN72+gpx2y7j88u44Xk/Xx2fWrnLsd/HirfjKS5oUs8d3r/q/mv7hZT3f191nmj7KNc3Q8eW+pZj5c9fm0SpzGov5w13Oml5xX7R+D9RiD/i9Lvrh6/5bEDtmfd9EpDnftA3vNE/Tyy9n5KZv/z/LTs/sY1NF97Lja+xy969vs74nP3h88Og/v9h5ADIqPVeq2XeYtVu1rq/QHq9VX1fqOVc6zlifl5Z3kv1qPzJ7XnTWPFTdfB3vEWdb105jk77P3IThf8948Okqf7aQ8zqPOIVf2PvptcDxmX0doRf13l/O/qr6Xt/o5cFXX4bNctxXx81HjdB45ZvZ3u88P7NYH9Orjs/jh6vuJVs339vfVzO++l0de9Q07rvo8m6WfXefvdvfHVdbHYrAfz6pbZu3/rFpfxBO/771eX60f6t1fZz3vdfY6Qtys52f5M262S5yU567nWZXvjWRfp5w9jnbQ347681X/yvKe0at5o53UX0viN1ff12yDxtkOjvvV/RZZ9lmtXj9dfW+8d5xns3O17+5VPU+92nnB2fuks/VB7/cvqvlFte/L7vL9o1nji+TjyDqerP6y2nx/1Xi07ik+K8jLj+l3il7+Anb5B94= +1438613580.950,1.000,0.303,HISTggAAAyh42u3d2W0bMRCAYQ0VIE3kNXWkNgPuII2mAwcInBcDwl7kckh+/4MNSSuKcw/3/PH+++0RPx+fPD//x7+/3z8+fv15AAAAALiXuPj51e3vGreWHNHJHkftdFbeuDjvOLh9+fL/7Pdf8erz54vfKy/k2dru1fjPjXGeX8Z7bmy3V//Pi/b6//63nXqNg/OJnXqODX2/8oOy009iY7s4Kf/jZLyWg3asHc9ROW/V+t7Z/LY3v8TF1631cjU/967f2er7LP3Zan1psCO56B2AOGc/8tEbAMgzACC/ARgu/uUpQD4CAHkMAAAAAGC9BkBeArBSfhj1Ok1A3RZn0I8CAIA6dde6EADWrAexuPzAEf+JzuMB8rs6wM/Mnz7Aj/g32Deb/uhfvIhjANbv+fMffUMcgJ7ZZUT5YzB7nJUnOukrkvpxKzlqPZ8xmx4iiR1GzaOeX5ZzXZdN7kg2j63nEs/in7Xzbpk8X9SSqzTSh+MQa9aLvc8bD3Yesg+fTQ+l0/zLZH402n6AUfrj2r83q91XPz7Vqj8ri+lFf6CPH7nOj1ZHI+m6IJLGT7b9U4B+vV99GGV/QK9xHX8n9531rlU8RpJxVvPfOPg66/rNOniO/raW/93tF3cdP4zGdo6k8pdGftOrjt6t797xMuv9HuJmu6ONHlfpI2Y5Hr7afjv9rXn1lO+u/QOlsx1a7W/Lfv5EOdlHl8b+dHSckA+G7LtGiZfRj3eO5vdBH/qsxPJZx69Z39T9uepc1vOx+OFc9ty7Xbm5L60tZ+lsr5jMj+867nf1e7F43D8q26PV+60ok9ozFpNLXzF3H9R7P3Ysoq/R84/nMJm3ujtOnAW/3fW7zhNfq9+Wf/m7erQ9D34ub9DTfLS6Djfb9YSz9l2t78ss/8hr6sE48vOrOf22Vx4f/TzLWvXzrjqQ5f50+q+1+oJs1xtn6bez3wdwtvOZVuljnA8F/iuf9qqn2fUjPwEAYH0AAPIlVvYnfiY+6RXgt+Bf/Bn8IZ/8rieGurJ//NXv5ynurukr2OEShX7ENTuBvgHICwAgr8nPEI/mB4gT8z8i319PvQcy +1438613581.950,1.000,0.096,HISTggAAAq542u3dTY6cMBCGYcq0corcJWeLlBvkIjlZlBtMFpNZBA2iAeN2meddzAhBG7v81Y+Bpr/++Pl9mn7/mt6Z//2P979vb9/+TAAAAAD+pyy2I0m/o/JxrfpTdrZXKs1nnBzPs5//WId92XmeWBlHefK45f6Pfjw29pfF9rN2WH5u3rDTsv3HSv/nlfGv2WPaGOdj5fNH/WhtnPNK/8rK/r36nU/6Rxy0w1b/Y6X95fZjp33XdB1P6jWetFccjANx8vi4OA61zgNn42w06mdW2IWdQNcAAHFe3gAA9TcAcQQAAABQfwIAAKizQIcAAAAAYJ0B+gDoH3QIQHzA9vtYRnmPSdAFcNs4zd8B3CVeiHfgt/yjl3VPJGt3NN3EIOOV19QbdIDRdEDP7NjT+IOdkVif2evZGGQcR/sb/CD1/KFvnZhPft/jOP1uTs56pPe6bzS/v6re4nfy/Uh28vsT6psz/Y9Kenv1822j5k3+2He91WvdV2tccTAObJ2/JNVLtvwQJ+fbc8v3rAtGWU9fvR6MRuehVxyp65Ejj4V5aXq+1tdXsjyXHI3zVq92iZ3bvdXxrftVphzIi7niUTa7yeP3yKOuX/Wtn637HL7/apx7zhfmjW4+oSSb53ix/l/VXqncr1rrwdHWTVc9lxUX96vVfYxSqZ4Z5fpBrfFEcr+4er+6ZKz10F393ryfazfbewyyPY8SN9fvXh26jqHfV6y/s9kpy32FXq4fZMu74lmO9WIMYu9C19Y1U3/3ZVvnuaC/IfSFsXV51To/bq7LXsYvTogLI8cZ+vj8uKjcXm86D358yM9q60M+GVNPvb6X4q7rOs9BQh0KOgTyrceAV9Zx7sft25bX+N8I/aI3QNxiD/WT/EQXGDuOe98l7jzfwU+MCwAAABitrv4LbUgIfA== +1438613582.950,1.000,0.090,HISTggAAAsx42u3dQY7bMAyF4VDOMXqXnq3A3GCu2UVvMF10VkENObZsU9L3L2YAw7Ek8pGiFDv+8fH56/H4/fhm+f4f//5+ff388wAAAMD9hH79t90YdLxxUj9KUn1FpX+v/W/t/63t1z631p9lZ79r56+1v2anteNR0cOyYp/ny/lLxR5RsUftc3vtUTt/2RgvZaPdYqOOX6/3XLnOml2XSntbdfBcuU550x41/ZbK+PeefzRPbo3/2Hj83X7V7BAH+/Nunilv9uPRyG6t7JylLmhlv9Z6y1YvZq1vs9eVrXQVg9oFoJu84xenAADzIjsBEF8AAED9AQAAAACw7gN6iwvxJO/Jy4D8AQAA+p4/zfd0B4yqc3GBnvNs0DPkOXYHvRgn2B24LU7El/zGHrjKP0GHkJ+q16c/8cpeqFEG0wldIoPOYnIdhzifyi9HfyefP8+1UwyeD0YZV7DPqe1nvX9klHwpj4tTuuF38B97jV93Zt/f631eO7rPVJLZK27WXTTWU+v34ZWLdTvr/HH2ewyDX24Zz+jvp9z6PtkYxG+j6z0mG0d0Or6t1zlab0UynY3+Pd7VddrV75merT6IQXQ5W902i76sC87ZDxBX7GLeyLMOcB/6vX45e912dd6e9X7IGCQfnNWO/Sl1xkjzpvpiznVAGUzPZ82DkUxvs+6r7p2XW83nd92n8Hq8nKyHo7ovO9tfLqrLsq/3o9P4bK2PbPV473XH3Xa0j9xHPeX5otz7Ca3uH8z6PXh05r9Z1ouR1E9H1wX2qzBjPB+ty3vLm37HDVfo/q51fZbnC7wvIXfdyh+54vbs+LtrfZBl/+Fqe4m7ueqjbPspfleD/mfyT6t828tz/rOs00e5j2/0/N/7/fPZ6nfzdl/9C3pK7Q92gnxl/LPXbZ5bAeRBAJCX++iH+QMjxxV9AwAwXn3bq73YD5DXAACVvPwXYwwHzw== +1438613583.950,1.000,0.102,HISTggAAAs142u3d0XHbMAzGcYNao7t0tt51gyzQEdsJ0of0odeLS8mSSJD8/R/iS2xJBPABJGQp+vL97dvj8evH44Ptz2t8/Hx///rzAQAAAODvdfJ4x41k9t+1nzh5vNI5jlH5XKlsV558Lir7L0/eLy/6tfb3rTL+2DmuZ/uNit/KznGXJ69Ref3389vO424HdRQ7dXM0f6LyftkZj737q9lXKvmwV8/Pjrvt9GMc1MHR+hMnf9/rn6PHLTfXvVfr4V5Kp+PW7C+N/Xm0nlw1jmhkV9zkn17rv0gyrkjil5LU/0CP+WO0OqXPH7MPj0Xibj4w70E+yzNxZD/EGYC8AwAAANC7f9CXAAAAANBHQVwAegcAAOZ5AOoUIC8AOhEXdvEjgDvyUp6uWSfFfW0dRNLt6RKAea6NPSGOANTD1Paof+aLHva6PxoYR/fyih4+2y9d0A89rLeOj0X0TP+YWRcx+Pb6yzHnj7Pjj0nrQnbdZr+u5OzzdmPyehCDxDFbHxJJx1sm0elVfmv13MrRdJmlfmQ5TxkX210Gy5e4KJ+yrpdmPR9eHnMz+3M4Y5A8wxrnA3rbGQfzhK7+Xz9azXut51f1MVcee44q5Mm685X8y9HHi3+fdc5q57+Ortt799e9/BQv7n/W7zOv1k3W9Wu2fmXW7/OvsqtcpM8s9aaXDmc/X5tlfrTebltn9FfX1K+zdpRkfp3lPkrn1cfoU9TXNfUxi3+umjeCvi/1z6jXyUXnvNsspZtQGukMbdfd4jTn+sO6iX0r5f9q51X9v8O+41h93rRuEPcR9j/aOqj1/a6r3Zef5bqGSKr/u/rM7eS4I1m+mf/myvdZ/UqndDSjn1zPp7/V39w3jt46cV2QfIR6ulqe62fAv/wKuhqhP5AXdKaO0gH7MINePE8OPf0U4kcH/CKPcChOnrfFLojraPEIcbMOgHhgLF3+BvILCGI= +1438613584.950,1.000,0.107,HISTggAAAtF42u3dQW7cMAyFYVNOb9ED9BY9W4HeoBftovtkka4GEMbOyDIlff8igwS2LJKPlOSxo++///zatn8/tk/2/5/x+fP9/effDQAA4EqC/exP0E7w96nzar+Xk+3uD+dF5XN7OG6vtBOV/rxVzt8r1/n2pL2otFMOfj767e2J3VHp716x77Hd/Ul7pXLd/Uk/y8F2juqkFu+tYu/2RV3uJ/VWGuVdHMyno+2cPX8/GJ+j8Ygv9jMuqlOvthcn7WvVn+zjTzTyp3naGP1bfV4K6xIAufMpFrWbnXPZRcf0AvHiV1zh/xBvADAOA5DfAAAAAAAAAAAAC+N7FtAnAADGN0AeQvwAeYT5KVxwS/7IN8D4yG7QB53JZ/4b2b5YxG75A7oA3a3tX+tB/RD/rx83+nzL/d5r7R1l/yN1yDqWrtjJ3zniSU/3+pf/AZhfjF0fy2B+e7YPY6/9eka/XzFKvpRJ41Q6xTl7XcvWnnntvfn1av2KxvnWav/dq+pyucnPV68ns++jPMs4NEo9XHUfxtn0kTUvSme9xcV+7RX3WZ8jXG1f0Gisu2ikk1H9u+q+dbOuD2Pw/F61n7Fo+55DwhXzTHEao97dtX6JTjq9W7cxWN7J2zH9aH5B75n6l2W+kP17vsf2S3Kd9RqvRlv3tb5ftjXSxat5cfa4uDmfz/a3TJ5HWceFePG87OvR3nUwJtFH6+vP9vxUlvF1tvcdI2ncZ7nvluX5mOzv749+v6d33R7l/pn3w8fKpzJJfO7Kj1Wfazjr91bHrZ6voI+R5nnZ5tWt/Od9cPVxpfl1tvV50Dmgvh38e7b3+3qtg7KvT1dbP4e8HYqymL30MpYuez+vKG4569FoeZh13pB9vlBu0gn6ztuNu1ghjvRIlyvbQf90y7/6B3Fjbzt77c8rzqAjOgFgHqAuYlw90CkA9VhdXGE88n9I6QjiCXTR7Qe5mAgK +1438613585.950,1.000,0.137,HISTggAAAq142u3dy3HbMBCAYS2odJFe0o7byEw6SKPpwDnYucjhgG8swO+/cCzZwL6xACnr+6/fPx8Rb48Pps9rfPz0/v7jzwMAAADtiM7G7dW+cZEda79fDpIjFuoZM/NH5VoWjlPTu/Z6mZHr8d/9y9frtHD8Ob3KzHw1uz4XzlPT71WOqfL3r9fnQnmnhXYuM/adXuYrC+Npzq7TSv++vv6s5FNU9C2VvPjHtxl5ykq5y8p6Egvlq+kbO+vJ1joXJ9W5s9a5q/RpLX+sfD8Okqt1PxKdj6+vZU+Mbwd+5h96AMfuE7LlgfyDdQLiCAAAWPfZAQDUGQAAAAAAAAAAAABwHxSQz+wCAFBvwa+AfAXEFf/yfzY/sL+6Bn5jX/A79D3ybUT91B9xCn7mB/LwP3+Jb/qzy375I7k+0WgccQ/16T72aVUfQjxcol8MlrdZv78jbhq3e7+nNgbNO/Kjpb/i4jjYK180skOWPiQGz9tIYtfezpGvOvds1R/14h/rZR/5ru8ay56j2avV99b3Tmm0Ll4dN7HSDtaVvvdd2fqL3uUIeQD18LT92WhxOaq8cXM7ZfWP+x7k7cFesXLfFeKr6Tp9l3zbWz9j47ijn0uvHaf3c4hINn8c7PcsfXscpLc+IWf/uTUOt8bnXc/L42S7Z7k/n/XcYe/4Jfl8Z9mn1X7Yvm+s/ijbetjrc9XyItd+Pvu61/s5wVX9ytVyWG/Yo4d5sz+fG+IlRXx4rrPvvkW/0ed+6Ki4CXkpnxP2L3f9nPnW+5zqeu75R/38yFn3JdbG+yj/LyHbcw7+f5u+WN/Ubn9tPULG/vys91uvY1nWO+dqYEfcNU7EPQD1a9+8e/te9ydzxYt9ey75xb04A+QPIJ/kHwB9A8QBAEB9BgCo2wAAAAAAdLZ//wuQBwdN +1438613586.950,1.000,0.125,HISTggAAAsF42u3dS27bMBCAYQ3V7nuA3iVnK9Ab9KK9QJEs0mwMCKJsSiKH37+I4UQSh/NmTEs/f//5tSz/fiyfrP9f4/Pn+/vb3wUAAADnEYPIUZLPvxXlZP2UyuPLzt+j8vdHzy8bx5WN92Xnenvj1463Plxn3ZErduTdOu/rut93jvv28BqV/lR25N+y/57e985bK/W5VOpvrZzn8qCnx/HWJ+Nua16xMc6WfdaDdtvST1TGQW2cLk/G6dH8U1s34uB5y0G9vJpX4+DxrercXfWv17obN9m31XWz9TP6ZgCA/A5AXsg/f/kXAAB9EAAAAABYDwGAfAQAAPQ9ACDvyZ/sDQDyEzsAAAB1lh4AyD/yHY7Ynf1zxxf70gf4WXb9BD/AgBQqAKAumRd/xEX6o+9z7UC/c/hdJNPTXevySDr/q/UGALi3TxulD2z13K4yqf+gb330up/mrucQRFL9vvqcx1n9PU4a56znNZ79HMK4WE+zrO9Hq1ut+qKo9K9W8s6el7L3SXGxfHf/H+7s+BxtPTpaPxOTxWd2Oe1XapMnY1J/a13/SuPxsvb5z+rpqn4lBtFrtnqSrY/qdf1Ij/qxM/JqsGvX6/B40Z7yRs51pjhi3zv7EXa4Z74xiJ3ZzfrvFflHixvMmXciSbzNms+y79eYpR5H5/EYF/tpNJL7av/2+WndeLPuTyyDyj/KfsZe62EMEp/yY9v8dtX5+irMrP9evy/d++cD4idH/bjbr/hRn3aO5P7B7/quU+yDDH7h+93kYPdx+48slEnis7f1UNmR46i8o+5f7rVPlA/NL+P66Vm5PHfH/Hqs463G7eW+CK/eHy4a93niRVzTH3BOHePf4AfgT/fO99nzy+R+4PMRcps/AIyT/+RFzFSX79o37/6u0A+fPw/1DNaBAP+H+3wD4th8AADyNNgZSBG3H8DGB/A= +1438613587.950,1.000,0.057,HISTggAAAm142u3dy3HbMBCAYS6UuIr0ktoykw7SRDpKG+nAPji5YAYDURSE1/dfOLZAAPvEQobJbz9//TiOP7+PT27/rvF5eX///vcAAADYiciuvcZ/dj8xmH572+fRfuKinlNWdx+Fnx+1X2TjROGaf34r/P442b40zn/esvu/FtrVxqmNG1n/R+W+3A7Zvuj4UrFDFO4r2b0kT/55bb6lcVKlfWT2SJX51+xVm3e60/9Kdr9V7Px2ZzzV/OZW8d9U0WctbtKdfhSV/JEezK+t1oE4GWf35r3W849G7c7K0Wo9Wq0uOzr596z10ijzisHnu2rcYO98CH5ALgDiDBAP7AXwRwAAAPUUAADAePWIegjiAQAAAFBfq9/tf8C+gLgEAMif89kj2BcAgOXrq9mepwOIW/W5/Sv7s8Pj4/KDueIkNpd/Ff+gV/FPX+bJ//rIrf7po4ddzhMm8S9PDiSXc7ziaUb5gx74s/0/f6TfrUib2HWX98IEuyxtb/mWvmbeB8Yi/fS2y2zvA1xF77vnidbr6mjr9lW/3e2c2avy0qvfExqDzi8GkytO+sGu5zBH+z7r1XktNZInLs5ntDwVT+5PfbNGnYU1/at1fl/Fr5K4lU/oi7yT1s+YY5/Sq+6/uv8Wt2vVb6PqffTn18jLff2k99/Ddv+/wlXjQVzPrd84uY+PTu348bl5p8nkPfv9ejRqt1rct5Y7vXjcdGCm/NXrfJS6ZA29hfGGXj/o6bn7cPtSeZTd5q07xCv417p+u9r5HfkDEJ/koxeMZ1d+IA6Bq/7q+Z/yErBKHgMA6yS56Zu+AQDyLrkANMkTHz4PCEs= +1438613588.950,1.000,0.043,HISTggAAAkd42u3dzXHbMBCGYS6kpIr0ktoy4w7cVapJB/YhPnGMAUUNxeXieS+coSgSux/2B7As/Xp7/7Msf5cvbl/H+H/4+Pj9bwEAAMDjxMHXn21HnDzurc+PjXbspb3I761zPjrH9n1/37X3tvH9t875+2Ac6/cvg/u21fHeuW/vuja43/p5951+XB9/Dq7vjevWmU9tML7o6DB6fuucvw/uGzvn423ln5Ee6/H/6JzvxUd05ncb+GHp2Nubr21jXD0a3zHIL1vzcjyYZ0avx5N5NWud22r3q+vrWf1HNZ3iYL2AmdYrqKE33XPEV0xmr3wFAPI8AABQnwFAfgMAqAMAIJ8CAAAA0L8DAAAAsM4Bf63tD/qA/oB4Yw8A+U1eAn1hHosj9k6oU/ADkC5v+L5GzFwfo4gd1fQN/i2Zr83772kX92+1/t481U/wy/7vF79afM+6DmJvrvmnXl+zX44X+dG6CPqSOvVhdntm7T+y7WPu7fdn/93Fs+M3kvgxqz9j0nxQLb+e/XuwV//8zlY/taUme9dnrZg91pfH0JLqma2+XXX81caXZd8+Jo33bH1A9brOjnP7AjqLl5n8wc859Ax6olD+qdL/Zdmfm22/GfKMfhN7dZxtfyjLOnnrft2r+914ch7t3Yd8dj5efR96tr9LVa2v2T6fevRzYlJ9s+vo/yFy6doOHg9dc/eD8WTcVqkXOHa9UDWfzlo3UCsuxCl9zA/A/AZdq/qJ/yFOjrt/8Jv8wz/8THcs1/0eB/EPABfNb5+gRQeX +1438613589.950,1.000,0.055,HISTggAAAol42u3dy5GcMBCAYVqss3AQzsCxucoZODfH4QzWh7UvU0shQEIS+v4LtSoYqZ90w8zq689fP5bl97flg/XfMT4O7+/f/ywAAAA1Ceu/ZZ6ofF1s/J073ispUx8pU97YuO7LzvVn508718XLeWnj+rRzXnzeT2we9z5vzfS3OCjP1jq29PUqV9oY/8/byzFt6GXN1MuyM3/srGdrfN0Zz/XbdUPeyIz3dNB/Uub4WihvvZ2M57N+ezTu9+L57PxHx+Og3a/KmzLPi8z8XPt+mquvo3KVup9Goc9Phee/Ks9Zf+y1Loyb5rlLD9GJvKP3IaXyuL7vWt+jTx8zTz1t/bM935gl/8/qhwD/ByCPAAAAqI8AAHB/Bvi7dUOcgD4xq//xXwCl84S8AgD6FvogJwAAAADoXwAAAKC+BfoiPTx+QvwOIY/8yB4t1xPsBvH9mPXEIHqX1yAv0C878z/yQz1177rO7lNTW0/+L3qf65v9OcHT9515iv+oI+a+f4/m5/qRZ9i19vOt3uyxLnMz+v5Qtev60fcXKrWP3137b17t20rvP9lbPNTab7PU/K375+gk7karB6Kyn9Re51P2/Rt1P9u7PicV9uun+0F0el1ctO8yqH5a1bFP7d9Gj+fS9bU+9Vl69P2iz+Nk1O9heO/VRj56t76R6rbZnsOrZ/oiFZKv9fO44EddzheTxpV8Q18zxmXv6+k9j159L1nqvt5aj9H4/FL1fuosrmrZLd0sf3R2fm95Ntfuo+qlN/tEp31Rr/PMWneRq22ctH6v4L3uHHLbz2Ru/fj9OH9Av35Vqz9q5d/iR/3FfgAgPwEj1rHiEezKfwEAgDpCnUSPAAAAOFDX/QXPKAfg +1438613590.950,1.000,0.053,HISTggAAAfV42u3dUVKDMBAA0GwQvYR38Tpewxlv4NG8iDeoH60/jBlIS0sC7/0wTqEkm90Q2o68fn59pPT9ns6GyzbOm9Pp7ScBALQoGjlfGIpNxyFfOQ4x2eaV8i0vPD5m2pMK7Su1e+nr+f91/+K4/P39fNm+FI4bCucZC68/TbZj4fhcaNc42daO/zATx1j4elrY7lJ8hpl8y4Xz5Jm6qJ2/cmWe5sr8T5XxzQvPmyvrbW5eWbp/7X63vs+t45tWbv+jroOx8nG9Xr+tO8R5y37LP/mrfgDo5frgegTmB+QDAAAAAAAAAAAAAABM+d06AK4H68VLHOUtAID1Wf3/8bR+BDDviR/GWX9BXeiXOK3XLuN/7DryPaZ8NP7txEPczXuYB6DHfPX8OnHpqV3XrgOisX630p7YKK+i0fw82n3YvfsbnfbLfWmfeR132n/u+c7Wm/toR26kzmKnebPWca3eJ8VO54XePn+Lyjo/+nqhV8NO4r1VfUUn79vqc8jVJ7gPcv0XP/1mL+steUrL+bH257zRWV2HupW/O7pPj4Pl1aM/T45G87rX+epo+Xq09WTsLE+hh3WEfK2ThQDML+Ijjhg/UFez51Pf5sWe4xmNjp/8wLymLsC8QGtxbu37NfklTli/GQ+MG2B+6f++AFBfd4/rL6SKB5Y= +1438613591.950,1.000,0.586,HISTggAAAMR42u3WwQ1AQBAF0J0oQAN6cVCZRAcaUpIOVoIWSHb2vQM34ZudmWnb1xLLUR7De4/7OtY6nwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgsxABgP4NAADYt+Usb+8H6HegrpPkEnL+5TvV5bf5RCN1kLU/RmfPy/Jf9aV/52a2fSg6yzkaPz+t74Hmgr4DOI9yBIB3Pl20mAYg +1438613592.950,1.000,1.546,HISTggAAA1142u3c3Y3UMBAA4NgJXSDRAh1QEjUg0QGN0sEhAU8ngn9iO3b2+x64h1t2k/F4PHbu7uP3H9+28PXz9sf+92v4/e+nt7cvPzcAAOCasNj7PuW6xAcA6wAAAIB9CwAAAAAAAAAAAAAAAAAAAMB7/r4CAAAAAK/EeRgAAADYXwMAAPp7AAAAsD8HANA3AQAAAAAAwIw8FwNQJ5FPAAD6IACsP4C6AKCeIC8BULfBPIF/56F8BHWbZ+eh/AXQbwCoO8ZBPAHUG3GEMfkXbsp380l8nhhP44a8QB4AqNuIr/iLK+LPM8YpiPctoqkydNyDeA39nPji8Rn93Ckslv+zxjtcfF1o9DlPq1ev8tzq7ryeZR5Yj/PuP3QeD/vG/8eldf7a/43d94SHxOWuvnGV+tdqXt4d37D4eF29z9n63jBZnofG49C7X4g395+vYrbzlDDJuIfC61vl7/SEwfO+9+t61bXWeXdXfvS6j1mve/V9Unix+191//H0z3vV/ioM7o/Czfcxqr8Kk8R/9PnAavOz976vV77Vjmvv8++nnJufzcvV+sFV+6re+f2U+veU8X76uOaO02zn3nHycW+9n1xlHQuDrzd1rjPr899Zn1df7a9mXX9K8+ZqfEOn/K/t92LjvCi13zTeo59/lc6D2Ph6w+D1MlTOj9T7xEF5WVsPRtXB2Pj1sz0P790Pt65zd/XLreMUC/uQ0ecMq/TNvfqH1eJZ29/Wrkux8XiPmq+9+9Je+85Y2L+M+vmF2fRe/3r18ftN8Vn193rufn511/zqvW+MF8ev9TlI73UkZOZVHJTXpecPMbFvO7vfWDnPzj43N1575rqVO8/fX8fROF9r+/DU/8uNa26fcSSu/0PjetDqOVwsnD+hMH/OHIV5H0/W6bPXxczx3yvzPHVe8/46jsr5lvq6F9axmPh6JPKitG5shddd+j7v69qemQ+586K2bzx7v+NiPp3dd27+X60PsTCftsLr3hL15qyulvZRe2ZehczrSn3/qKyfMbMe5d5fbj6V/pxe7jp+VNarVDzO5v+WWedLzxFKzxlq+6DU/e+F/ULMvJ+tsC7tifpe2t/mzq/c/rd23x8Lx/1qX5bKw9y4Xp2vs54DjX6e0fvnfsKg6272e06/AKGnCLs= +1438613593.950,1.000,1.237,HISTggAABFV42u3d243dNhCA4TPUugsDLiXVpJAA6SANuSR3sAaSPC0gHF1Iakh+/4MX8FmtOPchRR1+//ufv17x58/Xf2z//4x///3x+fnHrxcAAMDIxMn/zzbe6CTnXX2UpHoqJ+W8q6+v9/vYuW7v5959ype/923n97ad68rO5+/0Wd58vu2Me+8+2xv5y0F97F33Tn/bwb/7OminvXHHRb8rb+x31C/3rn9nx3LQbttBuT5O6vd10J5H7fc66e9n71Mq3e+sP96tf3fz/918X7v+Zq3ntf1iFHljUPtk6U9nvS85AP4M/jSTHuiZfxgnAAAAAADIiPUEAAAAAAAAPIn1KfB3gJ8AAAAA+nwA4h0AAHWMfPwMQJ84EYf0Se517cRuAOSRsetcTCYP1oyHEE8A1H80sBv7iVeAf9AvAPmA3saRw7pMH/3FIH4bk/gn6JWe55Mre/6bNd+VZP4mrox/JnnC34W44XfqKRau15G0/7yqt2DnIeQMdpDPyDOsHvWbufUd9JXaD61zzNlvqAPHPu8dX8GuQ443ktxvlHn77HaLQcdhPgR2Iw87sOvM/tD7PbVR5umxSDybh88xPwd9iWP5Cjn74RhcX7Osx8ZiehO38neG+eUs88KnxsHfII+LB4zdD2ffp736c6iorMfe881s/YXvbSbvCvOXrHqrlQ/KCzPF0SzPN2Mw+eRd8zjUr8Ot1xXsR5dPMsijD5vL/6Lz+oF4B/ug1zwM4g78grxr6+cpOxT+sMS8uJX88sda/hL8Ygl7117n5Q/0wK71/MBzTeNeeV606vlb/A/8g54xhp31aeIQeePResYccnpuAfBzcSJv8Q/6enq8/GkMPWX9Pp5W94tF42/VfX3Ol1mzvsUi/o02/uLcwL7j4c999ZmlH5l9HwO/7kOpFBfq+TF/9tyor1/Tm74c/e3k3LW+/eTs55G11qP8k0tv7CG/Y36/Cn47Zb+92vf6ZauDq74fdDcPtO4js6z/eA9vDv+Om7+nL79GaRwX9sm17eviYX2v8vxu9ueVUckPe9WFVecxtfzwqe+N7rXvpnY/0fvcp1r2jZP6eMq/R593jppvate1Ub9PcbU645yAMfNJq3NPsvVdV/e9tOqPaufT1Z5LZ8vD2c/7zqaHbHkvm/6tJ17L39njMLvde52DZh/csT4j6/6t0b9vlF7GipuscVAO+tXd9cNW67nl4nVxcRx35WvdB+xdt1XKN3HTnmfl3i7qsda69tn1oXLTv9+N+6jcpXJ+2U5e3+v9s9pxlGW/Ya/6fjaf1V4nLY3106pfyrLPo1bd671OmK0fGnWd8pUkHp/2g3g47q/qudzsD3rXnaf2RYz2PnvvelMr/iKJn92dx9auk63tOeu+0Gzv79xdH661zjnL86PW58Jlf871dP2Jxp9jDWY/f23UvlB8zqWfIJ/8IF8PN65W61Sr2b+XfPbZtxln630I8hnomz7S6vs3nPAJRQ== +1438613594.950,1.000,0.058,HISTggAAAld42u3dzXHbMBCGYS4odZEi0kFq84w7SHMpIx3YB+uEGQwpCSQI4HkvHFkiufj2DwBt+dfn349l+fd7+WF9HOPn8PX15/8CAACAejzmWUvKXrey46r6xJN2xpvvry/akzZelz4fG8f743jLzisdoxBfpfGVxlOKz7TT/lvB7tyO0vVzO++F9/P75Ne/b+iyFuxOhc/t1T23eynYnQr+TjvjKr/PuuHHUjzesvPXgj5b8bIV53vzKm2cv26M49n43qoTe8e77Mz79cU69ezP04t19ax6e/U+9Op4o5JOcVHdjrYrOvH3UaTB8gYYad1mHBDf4CdAPgEAAJjfAAAAAACAXrGPAADQdwB5I88BAIB5lvmN+Sa/A7C+BnqJY/kBAADMBwB5wc/sG9neqHwd9eEY3YM+7MJ0fmi13xn0B/QtiFPxBv6uokMazD8hHjBRnYrO8xL6oPHJk1bjH+U5QUweR/oLf7SsH2fVnb15PuvzzyT1pqgD+h27jqgLib7yYDl+X+WofZ/YGffRKM+utj8bg+flqPWi13leDO7Pd/9/eIj/LvrdWc8VxYO+MpJevc5X8ZxuSR6LR6jXb+4L1LIjBvdHiDeYZ0+rQ0yW561+Xz7kR9X7RiU/xCR6jbYuv9p9W893R+/D0TiOo7I/4iQdovH9z/a7Oi3PrUvE32j9yTq1D71i8nyi7zXt9T0wY8a7vwfva30zy/d16IN01B/ohLnnu9HZuMUv3QGoOzXq0mjroKuuX2fd34rB7zearuY5dKMvXQFxDH43XgAAoD831fcb7HQH0g== +1438613595.950,1.000,0.061,HISTggAAAj542u3dy03FMBCF4YyTXFECC3qhNiQ6oDyaoANYAJtIxs4lT+f7NxH3YXvOnLEdg8jT69tL170/dt/0P9f4vnx+Pn90AAAAexKNjTsajTvtnP+ofD8K447C9ffzw+TnvjLvqbL9lGl/2l6faXca11joJ6fLUBjXLRPHUIhv+vqY6XfMvJ9rb8joU5vHlHn/odBOn+nvNnl9zPTbFfKc8+lY8OFt8rmu0gepUDd9Rueu8LlhZp0MM+tr7nyRZr5eW9f3zptz57GonN9SpV7/XXfWWrdio3a2jv+s+xsss88D4L6RfgA/AgAAAHCfAACA9dR6D6hDAACsW+AfAACsa3QAAAD2EwDMD5BX8BO20Vm+AJhHjRsAsN18bR63jxAHAAD2EcaBrfMT/2w36Nl0/EGvVfwT6kw9X8i/a4/P/onPjhyn57CoD5zHZ/a96p+exn0V38ZF9Sydf53l+XytjKNV/7fyf0ud46wT39rPKV2qP+vgvj7Y+rw53dnuUc474mR+2Cu+WOh70Xjdun+ll7jWm1f2vn9Rp8Yl7vbnJXr9vX+Lg4yn1TzsdV8SF6+fVv0VdD5FfEE347e/a8ZPwQeH1N35DB/co9dVf38dfL2qnrXtpTv7ObrPj/L3mEt/z3nSMt+7yr46+ALy1WzcZ9c3zdxnqP9z3ZfGxXzvvJGOLeuyVP9JKg/lA/UG8z34D2deb4I/0HDe+JBfsZ3e8kEPutBh6bj5hd/pA/ANAADWefABAMB6gEPl5QvLwgfJ +1438613596.950,1.000,0.042,HISTggAAAg942u3d21HDMBCFYa/iMEMP9EJtzNABTVEOHcADPAk0SoyjrO3vf/HAKJL27EWrcMnT69vLNL2/T9+cfp7x/fj8fP6YAADAvonB8wW9V50n7qR7DNInOnbXlIV6lc73S2PcXI0/Vc96fGu+ep6Hhj31uue/+/hf654aX58a+5+reR6q9aKxfm3P3LC3Xic6+rb23Vq3XKhHb/y5M0/vdbW+j439RmN/9Xw9P82N108dP5QL8yQads2dZ/ln/vbysFxYl0pj39EZV1aqUzGo3vZ0znoOjzrHYiUds/YFe++L4kbrHb0/zX6/AOSBPIN4AeQZAAAAALjf0B0AAAD6KQCAcw0AoK4CgPoG8QIA6hwdAfnCDgDqBsQFxBP4DwDWqj+j/t+guplTX36A/LBvdfwYevAXlsSHuKELfcfvNzZudxwkLm7ld/VFncLt/BXiAPy6Gb34gX73oIgX9y7Iy53nbyTLv0iiXyT1V9Z6dq1uWT6Pzz14jD5Z8nrpuEiWJ5EsnrLFw7Wf87qXnztsvS9e6retfY7u1vy2NF8ieby5X7MLkGfyPWOfCvm4p/5dPCy758gPZNBz9Pvie33f171YPTqyXVnOv+D3Q9jn71z2EWdh/SH9xFr1OVu/GAfPn2z1Vx2+jz/9Po3zNcM+9fnsAgD1lJ72Ly7YS1dxCwDqEgAAzm0AAHD38/sL50EIYA== +1438613597.950,1.000,0.055,HISTggAAAih42u3b3W3bMBAAYB3peIru0q4WIBt0tuyRDdKHNC8EWLIJZZHS970INvRDHo9HSYZ/vPx+3rbXX9uH/HcbH5v3959vGwDADGKS68Ti/XtUe6Jxnhh0vujc/3ObOtvTe1wutp+eKseX582NbdmuW+U8t+I6T8V5atdv7Zcrn2v7le27F9/nyv61ftfiX/b3XnzORf9ulfZulfakSvzL86bKcbdG/LZKv6JxnnK87o3johGHVNkvNdqZOudx7jxf73zNjXZEZ/3JnfUldfar9/rxzevuVdd727XqOjfb+nrWdf8q47Z3e9MGAID7TgD1UxwAwLqJcQLzAQAAAAAAADia3w0Bzl3P1XnrNmC+AaijAJ7Xueb6GQddF76TV7H4vDPOAGvUK/XLOoP8WCX/wny79PO6eqy/ZxqHkH8MyB/5oB7rr/krX+QrjMrbvX5HjYOPP3rex6Dvz1YXY+frzBrHtNg4h3b8cxy177Hj5f33Wr6ah6vU9dnyMeSf57OJ8k/8z5H/ez0vjv5dRL5ds67Gg/Nv1riO7m807uesx6C+Y1zFa973AvLgmuMYxo0Bz/HGda3nvxi03yzx+Gp71YHHrDt7/09g9vfQR71/kt9rxcl4jY1fEpKl7x/9XqhOiOfx/ZVva47v1f4nvfr/U80z6wLqtboB1hXrFebJcc81jKk/veOVLh6n/90vLh4fz4G4vzI+AKjH4mi8xQvkLwAnWcf+ADvdB+0= +1438613598.950,1.000,0.048,HISTggAAAlV42u3cyY3cMBCFYRUl2yn44FwcmwFn4MCcijMYH2YwBwEE1d2UxOX7L0K3RmKx+GrRMv3j959fy/L3+/LO+rGN983b289/CwAAwJlE5ePiZLuiE//FxX6pPZ+0+xxPHr9kzhOF88dBP24Ze3P2rLv9+++3jP27Pv3z85rZnxt/K9iVO18U1qc0v9z4W2b//nypYM+X3fFr5vjcON8K808Ffa07O5bd55L/vmbsXzJ25UgP6ii3zY1fWs9c/GyZ8y2F9V0fjPdUiP+jeSwX/6W8kg6Ok/v70rouT45zVT5/9HzpZHuurtupk74BbesI9AQAQK26ot5APwL6BQD5AwAAABilj9bPAwDUAwAAAOhrAQAAAMD1U4vHAZg33wCAvDiXneoFAADqJID6cR8Xjyevua7jD/MG3cDvFkI+uEL/4ukcP/LrnH12NOrPoJcm86g8MUb8Bz/Q/4B9nPdhoS+nX3oZs468er1A72Pnidp1PL04Xq398WI8xMlx1WqfGE/6o1c9RyVdtz7vaExfo+fbo3EUlfTZut+is7zQe5yO0m/HxfGp35yrP+/9fsHV9dR7bK5f7yTxI93f6K8YZPxe+r9R6+uzdqRJ5jt7vfMe4DXzneV6vNU8eVZ98BxYn3qH/WF9MCCp0vV4Kd+nyvqd9T019+nqjt96Px6T6Ka3+cVNeqs13t3PAaLT9X61Tp5tzyz3F3rLZ70938BYOjxar9yfnEtH1ofeRtQHXY+1XtYTNfTg/6NgfQFAflZPMLIe/L6d+JfH+BsAAACYst/+DzOYB78= +1438613599.950,1.000,0.046,HISTggAAAiZ42u3d3W3CMBSG4RwHkLpDd+k6XaNSN+hc3aUb0AvaG0uWCSEQ2897E5UE/3zn87FjUnj9/PqYpu/36cL8d4zL4Xx++5kAAACwf2Ll+We1M13ZzlhZXhSuj+x9KVsX59fNhfLy95fOR+V8qfxD4fpjofxr9UmV+k+F13N9/v9+ydpV6t+88LqSjqlQXq7XsVBuyq6fKnHI9T4V2lPSf674ZS4cS/Gq1XdY6N+5MM7mig/Swv5PlX5Gpf81n9fyRVTiNl05rqPS3rX5Ny3sz1I91s4nt/Zr7fu3mifvRdr5emBvegE9+NJ44SfQn+4A5BeAnwDIM/IfAACAdRPdARjnAAAAAAAAQJvYHwMAeZUO4EuIO1+B/wEAAGDdBwDyGsQFwT9Dx0uc0JMvem+37w+CPKPdo/shNqpntPwafN9EfOmOEeZJPu9rvWJ/qQ9fG5fb6hp05mt67Pb57kf/7t2t9wPPvq+NB+sXxuGQ7R09b8fgetw7P/Y6vlrdV2plvzOerHNv9/XmN/W33L4wrrrI++JCrxb0i0bjudd2jzrPGq/Y0lcxSL2t5I3Qv6Hy5qP1s99v3a6/dKbTGPNGK/PtXvcJ7/V51V6fC7hXvb19XrW1n1v/vsQ0aJ60PuhTh9HuZ1p9Lsj4a9MvvTzf0+t+nv+7lH/o2M46nw/pA7hvBfgf/Av4HSbxB/h5XTlbP1dvnMpfz+w//4G/IJ4A8KA8+gu8BQfx +1438613600.950,1.000,0.071,HISTggAAAlR42u3dy23bQBAAUM5Q8iE1pJe0lWuAdJAOU4E7cA6OLwQW/EnaD987WLAkUrszsx/asvX9959f0/T35/Rp/n8bn18/Pn68TwDAc4T2cyDeMUg+4sHHb/0+NsZ3Ljw/Vx5fa++8sR25uC0dv7z/Xjh+Ljx/Lrzu8nxfj98W7Z0Lxy2/nxbH5+L8t0L73haPf93/bSWOWejn/WBcs3Bbis995/lKz8+V+5fneyvEIQtxzp31divEfV4ZT7kyDkvxj43nm1buj53zTxbqdut8Mm1s77QSj0f1Z+vz8+TrlM5zdt5/9XHPXrfPtisvum+Mxtpj/448tR83cQQAwPUD6gDUD+oYwLyBOgUAAABcb6N+wLjSP+RPfQKA9RUAAMbZZ9t/g+tr7T72+nGRfKhLeUJeYOQ6Nq7E44r5CHHSH/Gn0/VavWFcIo/aiXphrLrwOZP6Ly71Xsf4e047jIu6eT+bD5/j1uc8V/o8zt7zFQ+Oy7P7WWtdGv19O9aVc7JynGPQOojBxsvezxF+1vuwa+9DWhkH0VidtLLvufr1W63fE7aWF/+vRZxb7ketv1OKRo4P9XCoHVG5foxv/XnlOH/V8akuqu7zev255KOuk64yb+fg/bPOcKV8q49rrLOMOU+frYes1O7W17VsrB5q7VNz43HRWX1E4+3vbdwcjfco8c1B87p3nPf++71R389gH9nHPHmVPLUyzsI4q5r/GCTPvbfD3030GS/riX3Vla87AF61DwX7C+RL/AGwDgDGMWC+0U8AALBfBsC8DVB1PvwHcEYIPw== +1438613601.950,1.000,0.072,HISTggAAAeN42u3dy1HDMBAAUH2ccKUAeqE2ZuiAEmkgHYRDctKMkJyPI9vvHfAEhC2tVvI6wPDx/fMVwu97uMjXY7x8PJ8/TwEAWLcoBIvEJ4r/TXGKd7br/Xoq2uXi9VS0K7+eina9/U6N65Wmon3ZLlfOO1XGmSrXnTr7VxtvrZ/H6/Gt+PyhOE/v+XJx3mNjfnqPtXmdKvN5aMQ1VOKaGu1TYz5DY5yx8/tio/+50r/cmUe5kV+h8jrMjE9tPLkx7kft163rzt2PwszzxMZx7n3n0fFpjfNZ98O40HVGua+rK8QXrB/kl3yzvwAAqGcA6878AQAAAAAAALyen2+KC4D9GwDc/wCw/2Pe5BtYn4wXz2ie5Z04iJe8hmHyLFo38gvk8y7HHcVX/ugfO4+/fHtN/KJ8kCcr7p88Ycl6HM835nG8/sgj9deI9XActD+j122950uD5du9/w9zq/vz1uq2vT1vb+X3mdQJ67xfeP5cRrJO5OMG+iF/xZXtzlfUD/XZQHG1zv5vL57LjFO9te/xqM/cF2Dr603d+5h+u2/bp2+J01r+vjZuJD/Vn8azh7jcW197H0Y+Y/8Tn3Xv6+IHAKgPAAAAPIcgv/ayDqxb6wZxB6x/AAAAAACW4X1knpJXf4EGB50= +1438613602.950,1.000,0.034,HISTggAAAhh42u3dXW6dMBCGYY99km4ie+naKnUBlbqi7ig7SC+S3qBahoQf2zzvDToIzPx8MwaMdF5+/v6R0p9f6Z3ysY33zdvb99cEAABwB2LjcbFxvLzyuPii/dFpXFvbVIlXfDJ/0chDrlwnrxwvKuOkRt6jcf1o2Pm0OK5UziuV36Uy7qOx/Tf+88Kf2vFl8Ts39j9V7HteXP9bI35lo/+PRvzLyvPLSn2lSjzzRn3W9i/Hf6y0ozT0WKuDstHO2HjdaNRXWllHsbKvLPcv854a9fzZPnV0n4+N+9eSdx7vqPnvKP9nv98BXQB0Caib3upSXwAA8xIA9c9eAAAAwH0wAAAAAHgOAwAAADDyfbfnA3EAAMB8CAAwrwD0DrqDuN453vJOTyPaHXSNCeoqJvOHLuRJniH+98xT0BO/cXg+5HPfOIX4DtmH1BHUAT27P6YXzKf7mFSnva9fxKBxA+jzfn73cv29+3rcXB+9P9+fPY/HRfnKg+opdj4/BqufvePWW93ESXZ89X914yIdxM71gP/3w9xpfe+lw7yxTmZ9v3lU/Yz6PVFMktdR7LPeZb66Mg6+V+XPSHHI8k9HE9od8taF3aH+9Rd23T7vIT78u4H/+s6xcTlrnfSq9aSz1y9j5+PupudZvmeQ3z79NJ+Irzj119fXkjuNr/XEc+/zRn+fqU+KJ/8AQL8GoM7EG4D6A4CL+uxfBC8IIg== +1438613603.950,1.000,0.044,HISTggAAAhl42u3d0Y3bMAzGcVNOihuiu9xsB3SDjtVlukHuocmLAIE2XMeS/fu/GM45EkV+JBUlwP389ftrmv5MT+bnNf5dHo/PvxMAAMASYrB5YuNzsXHeGDxe5T/NFwv9U5LXX9waz2fjlcY1s7Mk9t2rffZc2Vk/PzfWMVfjteyfk3XU494TO1p21+973X807Kjv6/W35ns9/2OhH+fEL3Myb23frfH3lr+mxuv3hl3ReH9Lr5l9kaw3En2XZLzMrrIyT24L68uc6D6rH7GybkUS16Xx36tub+1LsfJ+a584uu/FweP00vcBAHAeAX4CAHUSwLXrgToHqFsAoF4AAPQj0AEAAADsBwEA0M9AV6AnAFD3IB7iAdA/+B0AAP0N9AZA/gPyAKBv/sDRcYqd7Ai6BE6rb3nKHyOuP8T9FJTB4xbyl/0Drufs9U49xxX6H9SbHta517lSdBbHoKcu9vk+p/el5xjML0vHKfK7y7q6drzoLI7RuO6ll7Od6/XSD7J5y0b99h637P8mx6A6vYrde9e3d+33YxB/Hq0L52LXqhtn2zfEyfI2TrIO9KnL4FdcOM/8TqTvzwm9zkc36rj+z390OIbdIc/lR0d6eNc5ZunMH0fN0/v3faPV5V7tWFv/z1oX9cEx4hL8OKQOYxC7/I4I4gM6A39v3+/TFd3RCwBA/wW/iRsgHwA6vHJeqAcAAH2hA79/A5E1B6Q= +1438613604.950,1.000,0.045,HISTggAAAiZ42u3dy3HbMBAAUC4oKU2kl9SWmXSQZtJDmnEHzsHWBcmGpCWYBPjehWOTArWLBUDqY3/98fP7NP3+Nb2Z37fxtnl9/fYyAQD8TxysnbXtxiD5a/34SI6Lnfs3Vj7P7Pg52X///fV9Wx48b60stHPff6m2c9LOXP1ct1+3Uz+u3t6Pu1V5iGR/SbZZu3dfkuNuSVy35Phr0n+Xf9/f/BVHVO1H8vg5iS/rn0uSl2vS31kdZP2atTMn7dbnn5I4YmF8lKT9y8bxUhbOV1aOn6XxGAv53jqvxMZxvXYdar1OrY279XoQG/PSy3VGmT5XfHB/HOx5HvX8ZbA8uA8RH/q91bqCOoVQT+IEAAAAAAAAOBHv+4LxDwCA6zL0L+pKXs73fWWA1vNZ7HRe6xuoMwCsJ9CqbkPd02H/q1Mw74gf1NnZ+ycGryd1C8cbB72NyzJIXObDbXmQL9SFvOydD69zWFdGzl90HnecfDyZ989xnxb646n7e81HDF4nof2u6roMEm8c/HFned1klHm/9f3nR/+v7WfX97P6Mzqpv1HG0VHvF0f7P7Zxkjoa9f4lButv9+f6dY/rAB673pvUq/Va/DRSpIBpvM+tms/lqYe69vdnjH/zoXhaxTl6nsqT83zWzzeZd/aNu9fPu+0V516vP3qdZIz87PX5Ku8/qMsz5s33Jftc74Bx1xPzCbi/Nk9gnZFfcaJuAMC6AgAAD183/wH0dwhd +1438613605.950,1.000,0.047,HISTggAAAgp42u3d3W2DMBAAYJ8hUYfoLp2tUjfoMl2iu3SD9CF9smQZUiBgvu8F5YcA57szhKp5/fh8T+n7K90Nf8u4L263t58EALCFOPnxxs7jnieu13o+N56PmftR+7yY+b4ozodT8ThPPN7cOK5LZbtjsRwqy/Jzr8Xjcn9z5fWh8v5LY3upEq9y/ZfKdq7FMlX2c2zEp9yPayW+qXg9Kp9/mbg/Y3F8tXEcGtvLlTjGxPVa67fytza+YyM/WnUwVOooNeLbqtvWdmNin4sH+0dMrPd4sN9OfT5m9qNH55de5uNYOP4sm0chzuC6CdQN8gdAPwUAzN8YbwDA+QXiBwCA801xA8D8Yv7CeAK77Cf6DgCYHwEwf4B6A/QV/cXxn3c8jDuoi7OMh3GVL/LTuJ89HuIkHuKE/Onjelxdym/2P86+b2bJfjH39/16iZf6MU85/v3HRZyPPe7P+h417zQP17o+i53W0VbzbTw5/uaXY89D+SDjtdbvlC/dP+Kf+99b/mf12EUd7f0+bSxc31ufV8TB89h9fOdBz5i/OUZ/A/XXT30t1c/X7kP6mPOXZ17HzV0v5Mcht3+0/y/me116yrve/z5S/TiPOfNxxsnqduvrnl7uA+w9L0O96rsLWOq+/Vr3t1236JM4/8N4yz/khfEC1BvG0ziDfAX0DwD9EeQHyH+AA/brXyZZCE8= +1438613606.950,1.000,0.044,HISTggAAAgd42u3d0U3DMBAA0JzTdgp2YQ3WQWIDxmIZNigfhR9LJydtQhPnvZ+okNT2+XxOS1FfPj7fh+HrbbgZf49xO1yvr98DAMCS4s7fx53ttK4rncUvOy9mnjc1buXO58sej0k70TiW5PzS6G+p2v17fKrOGye2NyaPs5+fquO5MY4hue5SXX+a2K9snKV6vkvSbhaP+rp6vk6N9krS70j6keVhSc67TJyfcxKnkpxXkvi28nFM+lmS8Y6NfBkb10V1fTTybpyY7631la3rIZnfqfWtTKw/U/efmLlPxMz6PCx0fsx8vrntLb1PL9X+VvbfWHicsdK8cKz7aOMQZ6x3kOccff7lFWA/Qz4CALhfAYAe9y/7KPIO5DMAAAB4/QsA2O8B9UM9BOsTAPUX7sv73j4Xbt1jXxI3oN915v/Z1Efkibw3T0eKV4iH/sPB89B6AtQX5Nl67YT1J582PL6l8zoWjnN0Nu/xz+3Gyu2ob4/F/dHvtT5anY+NrsO9rYO1vud2L3GMTtZPbLweRCfrZK3r/J3CuHuOi8/DrNOvEC95ZX7EE/m1o7x1P/Cc1xNHWQd7fz+1tzomTvvM81rZaBywX8svQH3p5/6l1/c9y8rxeXa8vN+5j3Fs9fVHyJNdjc/9dd/zb37lhbgB9FM31DXkFWA9M3f+5QXqFaC+AADQ/X3gD2EtCA0= +1438613607.950,1.000,0.039,HISTggAAAgZ42u3dQU7DMBCF4YzTcgruwtmQuAEXYsN9uEFZlAWyZNlJ0zZ2vn8TAak9fvNm4gREXj8+36fp+2u6Mv8d43q4XN5+JgAAgH/7g8OtKxrPj8afr9U1OstrbDRvujE/0TjfXDkvZcd54by1OPP5U+N4UYgn55SNm3/uVIkzFY7nwrilz52yefNx8njO2fFUmHcufF3StaTfXBhvqsxTW9dafSL7/ksh7pLPUkWXueCTKMR1LowfFX9FYfyo1PvcWM9pYb+Ixj5Tq9NoHDcq86SCTmnl9ai1f04L+1Yt7mlhH1kb19bX0Vg4ztrr57wwnq2v24/ex8VKP8aT4rv3PL3kbZT7gtjJONGJXu4/gfH8FnSwXkDdgd4AAAD2fwDUGQAAAAAAAIC74XkhAAAAALhPAn+OWifqGQDgegJ+hLwA6gaPz1/wAR884XyAj3BUn27VX/3/QvH2tK44mI70UU9A8L3+Ar4B/Xe4nuhcb/V5mw704/ce13G09yWp033mcTQft76Hz/NP9b3Hdd67Hp8Vv/7/2PuftFO/x4PH770/qKd95CM692F0onPiw033u/q2/icv/Aa+GEnvo7+v3N+5q/eRdAr5xAHq3O8/x9AjBs+fPspf1kkfeR1Tz+CfQ+2bPK+H/PIt/cAf6DVfIR4A0PfAVwDUP+gLAAAA2B/vU+dfoPgIPg== +1438613608.950,1.000,0.045,HISTggAAAdp42u3dS07DMBAA0IzTVCw5AHfhbEjcgGtxGG5QFoWNhWWXNq3jvLeJWpLUn5mxlSL15f3jbZo+n6ez+ecY58Pp9Po1AQBjC/28i1RoTww6n1HpZ+n91Niv0uvUOA7p7/1vsX1zdkyNn5tftxTOnyvXzZV25fc7ZO8fG+/36yk775jdr9T/JTvOWXtq/Tpkr5fK9bV+p8Z5TI1/Xwrnp0L7a3FyyI7HSlzU2lOKi6VxnGrzWxr31jwo5fVSud/UmK+t9SAa86BWx1rrbjSe33qf+Z91r3V9uHQduXbdWfv6e43LXvZT2F8DAID9OMBYdU/dBAAAAAAAAEp8nwjyBlB/AADAvpOtxoF4RVyCvME8i0fE0/7iWZ4iPtnqPIgPYCv1Qb3qa5xCXIibAforftaNH79DJO96lMSfeoVxND7G33zuZl8Zg8+n+DRvPbQjxDfcPJ739lw+Nt6fuPD1rdqx9vOouFM7R//9zjRIvYiV52O0OrN2XloHH1uP6LMuQY/ruv2zusfjxtW6/5h+z/KHHdVd8Wt8e+5HGDfjOED/t5o/134/sPZzQvtguD5/5Yl52eK8PWrd6WUdC/nXRTvkL8gHEP8AYB+g3QD4fwYAAAAA9iy+AeGbB5s= +1438613609.950,1.000,0.049,HISTggAAAfR42u3dy00DMRAAUI+TAEXQC7Uh0QEN0BGt0EE4ABeLkRMl2eza710i8tm1Z8ZjgxTx/Pb+WsrnR/mx+32Mn4fj8eWrAACcIpKfI3l9LeNcOh5rGV9ZSRxOjVNtXq/NY+l8rjbn3ey6JbnPQ+f+D831n5r3/z1/SD7fXqf+fz4v+854Dsnn2/tH8vpjMp9sXk/N+9vx7zvjyeLQzrc042/jcmjev0vqIzrxbe8bnbwfknGcer2S5D+r42xe9cL+08vDLqm/2un30RlfdK5Tr9wns7jtO/epZz5/7b4eF/ZZltm/lz6fxJ3uW9Sb9SH/ANP0edQDAAA4J8oHAADOawAA4LwL1gEA4FwA1jGAPjZD37QfiBsA9hvkHwDY5r7svKD+zA/QH+frOzF5fu0b6mykvhPifVb8Qt0hr+pmJfO0Lseqb/kUF9Sx+td/uF3cQzw4I84xWX3HIOtga9/niMHXz+j93PeHbjPu3v/NZe7zQwx+/630zTr5+llLnmIj85o1n1uLx+j9del5+D3eOYPt5tPf8eX3Fvvp7Pus9WGfNN75+uS98qbfoK+Yv/MU6kndq5dl7iM/jNDPQnzvGoetzq9ar8ijOE8+z5BnBs6fulhHXORhHXGWB3UB6so6AetU/EC9g3UA6hmwvgEm6rffmXcIOQ== +1438613610.950,1.000,0.036,HISTggAAAex42u3dy3HbQAwAUGKXUppIL3ZrmUkHLskNpQPn4M+BEwyoyOL3vQuHtijtYgEsLXnsn79ffg3D6/Pwrn8c4/3w9vb0ZwAAWELc+f21xnX29WnJeSTHoTjPvt6SY/W4+Pd97pexeJ6ePE9PHtdmXt+S1xuTeIzJ9z/PL5PzXhxbMf4xGedlEq/P666Tx1+S55uO81qs31ic9+T1WhK3nqz7UMTnWsRtus4/knlFMZ6YWVc9GXcU8Yhi3Fn+3VrXUYy/zewzvZhH1of6zH5Tqebdi3V51H6z9L4UK413K/NE/OURANiXxQtQt6CeAHULAAAAAMB2hPEfIg6gf4A8BvUoDiDPAfQp5AnyaA/jVz+APgvyFdQdAPqzOHHc9YwHXy//9xWfUEf6ifhtZj7yA/W23Lji5OsY+o59Gvlx8rhaT/B+l/UXd9S1+Vkf899PHNb+P6ex8Pzu/f3/+OZ53Hpdmzke7tMWrr9YOJ/ayvV7tM9vlu6j6v4cefaoPt+kBu6X9VU2+XOKPJYvR17ntfLd5zTfe9+pzxyzz3t/SZ/+n/mEOMlvYNX6unX/3tv9eCw0D/1xnnby+vN35dy3bHGcoS45UZ3GzusV+4z84uz3TXHyuKpv+zjiDwC4b7Cu5iOeoC6YsY5/AWYXB9A= +1438613611.950,1.000,0.035,HISTggAAAeN42u3d3W2jQBAAYGbBXIpIAeniajvpOriOUtF1kDzYeVlptNhGhoXve0HGGPZnZlicSH7/++/PMHx+DFfjbRvXzdfX7/8DAHBscbD2x86uHzudr7izvfHg+R4dp5/XpVqnDtX+lrE6viTX+9k/NY6/VMdNSf/fbttf1XGXql1jcv1qXT7M1f5LtZ2rz8/JeF6q12PS7+w6JenHnJxvSt6P5DwlGc85eX9szEM2fqXR/9b4Tkk/sniNZN7HJJ6jkVelkS8l2R8Lzz822j0k+bD0/Ev3Z/1o5XmrHmVxNCxs17RwvuLOevxsnY8nP/+q+9DW7dtq3bC39Vbv6z/r83P0X5wC6iWAOgsAcKT1lPUV4LnO+IJ8AQAAAAAAAIDz8vdzAADAcwKgzumP9kOf8S5/xIPxMr7Iwy3HzfjJlyNcTxwjb43PWu+H8ROX8pyV1tEhPrrOK9/X76PdcZB6sHV/4qBx/qp5k++w3vPX2vfpvf2O4qP1trw4r8eT1GnP933WB+PfV/uis37t7fd7rePUiSOf79nrFfEkfzuez7P8rniId+vsO+q3OoE6gPEEca//AH3WVXWbM8bB0u9j5EffdW2r/58XN+5/rDcv5gl5bbyfva64QL0BAAAAALYQ39rBB8k= +1438613612.950,1.000,0.025,HISTggAAAdZ42u3d0VHDMAzG8chOOwW7sA5bcNyxAYswGhvAQ+HFdzqHUtIk/v1fcmkTW5Y+S0r7kIfXt5dpen+aLtTvY1wOz5+PHxMAAMA9iZXvG8U/S8eNG89bmvPafL50vrLQ/nMzz5TM93N+as5rch7N9XNzXTt+ba6rneOczFc69rT3nZJx58TOdp658eOcjNvOF8l17TiRHGsSt1MSr9pZV+3Y2+pp7ug1i8+5o5+S2JHpMRbqNRL9lySekejhnPildPRzau7v2d3TXe2M0/s+Fua16OSZpePVf87ncaV9vx1/r3X3VnUqVrYP0JcC6+gpNq5z++bYfhvt94ygQ+uybgAAAEAfCwBQDwAAUG/VcwDwHAUAAAD9JEBnAOQj8CvoBbBPAToH//EPAEAdwo7jHvRj/wH2F8SfX0AP+lrxwCH2LZ3Rj/VD3PjlyP4NdgD6SftafCAuoAuI05Xr++v7lPRJ29BV7NRPW3nOj43ZF4Po2Pti/4fS8Ye+5L51LgbT8VH7hVHrxqjxUKdwhLwa9H3I58299eH0sY/9Nfr/acHOTdknb4gX1otDDK5fQN62T61v2frk3XX0Ueh/E+sK/gLkHf2M/MUf/ADx3pEfQjz3qasvEnUHgQ== +1438613613.950,1.000,0.069,HISTggAAAet42u3d3U3DMBAAYJ+TAkOwC7MhsQHLsA4bsEF5aJ8iLKclaRL7+x6I1Obn7JzPTluJ14/P95S+v9LFcN3G5e/5/PaTAID/ic7iDvfrUHHESvvFjcdPj8uF12vnzX+ua8vXm+6fKtc/Xbdj4fg82S8m+w+F96fnHwpxjIX2jYX3c+G6Q+H1XOifp8k2Va4zPS4X4jxV7sdQif+l0B+1PBgL+TBW8rKUB6kQb67k51DJ71SJM2aOp6jEN8y8D88zx09UxslYGb+18b1U3YqZ9WvpOhszt/e2b6n4842vH32dcPT1C9adAOoWAJjPAAAAAABa4vNcAAAA8FwNAOZdAAAAz1sAqNcAmFd6m3fMr8BS9UM9aav+Rif3NQ46nrIhJJ8eEHc03o8hX7vop5Afxpn26FftWCW+kBfy0/Pqw9bha/3fNPksXjwHuV/HPn/s7Py1+TkazfdYqd9jpfhi43xpvS6uned7XwfHxtfL/+wnedxXe/ZeF/Y6b64VVy/fe+aN77Pn023WV57H2liHaQ+0V1/8PhzwObq4xYvn7mONB/3bRt2yDmeP9dbnMPts/72/hzD/HLvuRSdxIc9pP096+5279TfIQ+QtAGD9gP7UXgD1VD8gP0BeA2DeMe/JCwAAAADYk/gFln4IXQ== +1438613614.950,1.000,0.035,HISTggAAAc142u3cy1HDMBAAUK/sACVwoBdqY4YO6IiK6CAcAheBRgr4o9jvXTw4xhKr3bXjZHh6fXsZhvfH4WL82sZlcz4/fwwAAMwnrtxfkxY6fzSeL2aaf1z5eips8/nk+6fC/lQZN2X3y1O2jez1U2F++fin3+/Df/ycjzdWtqlwfGm+38ffZfN6aJxnqow/ZfHNz3NfiMtUmFcprqfC+UrHDYV5p8Z1HgtxTo3jpz+ua23/UMjfVNlfWp9U6Tdjpe5q/SIq442VPpSu7GOt8xka4xuN/XSY6bjer2ux8nhbxS9ufJ3cfwHoL7D3+gn1DfIb1COsmr/yGQAAAAAAAABun8//xYfl80IegT6BOHPcfJGnAAD0LLnvxzrAKnk81/8lDPUP6L+L9B39C3Xm79AP6Pl9gvzklvqbPEWdHU/qfD30JfXA/Ovme3hwnLwO6yBOqDd1IB93MI9e3h+u/Xy5t/v2dPA6iBuZz9bPkV1Ptu3TvfSVXvp1L59Hx87iXav/NNN5tq7LOFh/APlzvLjHQr8nX3B9w7pYN/qPr+8hoi7gf/kof/UJ62L+WO+WceWbOpYH/cXR8235A6gvAHB9AwDcLyAfAXbUBz8BXd4HrQ== +1438613615.950,1.000,0.047,HISTggAAAdl42u3b0W2DMBAAUHyQJkN0l85WqRt0ru7SDdKPND+WTnZKUgi894Mg4Ji7szGN+vrx+T4MX9/Dxfi7LZfN+fx2/QQAWLsy8/PhwdezzziXZD+7z2gcL41t1s7186la947V8Wj0+5BcN3X25yX5nrq9Q2M/Gv26Hj9W55+q86Jq91hdH0n7Ue0PyXVT0v6YnP+SHK+/Z0ry2YpH3a9T0k7d30O1jaS9kuSrNOotkrrM6nls5KM0+hON8ZXFLYvj2DivNMZx7zzYO5/EjfPO1Dmv9O634jf3+R2d12X3HzPn9bjTdbe205uXez2neuvH+gRxkxdxAAAA8F4CxgcAniOgnkH9A2CeBwBgretO60/vGQDgOQcAANatAABYb1q3gvkDML7ZbjzVw7byEE+W/7KzejTe2GJ9qGvY3zgq+vFQYb7FOBCnld6PemML9e//ZveRd/llz8/xR81zZWNxepbn1VLxiYXyMC48bnr3l6qnstHxsrZ5oewkfnPnmXhQPGNl9VWeZBz4ffU+6yPvEagH+RIf1MH2+1n+eJ7fx5Fvls6jeuAZ617dArDndffe/k9q6fVr/NN9hCHjfdn9iwvyBQAAAADchb8TAphHAQCsd1hNvf0A1r0Img== +1438613616.950,1.000,0.039,HISTggAAAet42u3dy1HDMBAAUK/tJE3QC23QDjN0QE/UQwfhEDgg0EgOxLGl9y4ZjK3vamUnmcnDy+vzMLw9DRfT52tcXs7nx/cBAKAlUTgeK9V37Xm1ppXavdSYKT8Wtj8qyx1/v8/9cd6cnDdWvh6S8uZMfWl75uT/abnH5Ly03ClTT9rfOXO81M9jppxI+p22Lx2P9Pqvck+ZcqdCObl+zZk4SI/n4mUqxMmcKe9Q2a5TZp6ich7TOB0K1+XKz62vqGzfVJjfqXI9RiE/HCrzxViY36Fw3bX5OSqPx5X1l/LV0nbeel8aF5b31/pvtT8NG6lv7Xpjo/0H2Gp+BvEKgH0LxDMAAAAAAEBvfO4CAPvbt+3fAAAAbT/3gXjcTv+sSwDsp8YbQN7UX6Cv9S9fiA/Mf+9x1mretB8gPxk/433fcent9xKis3qtL/lBPwGgz/3Hfe867al9nir9viL3jYf//t7y3t7/iIV/t5YHrcs+n8di5/2Pla93X7FsP2nlc87Y2bqLleqPG41HK/tvr/tKa/nMvHquN74g3hA3mG/xCvtab73ex+/1e49bff9QPm9zXt0nrNuPMO67jAP57/s4GA9uEQfed8b4QZtxv/T7s8aVLcy3+JAHQTwibvXLOAPg/gDELwAN7BsfXN8H7A== +1438613617.950,1.000,0.031,HISTggAAAdR42u3dwU3DMBQA0PwkpRIzsAujISQ24MIuLMUGcGi5WPq4LWmT2u9dLIqdON/+jhMk+vT2/joMnx/DwXQs41C8fD9/DQDAbYQQiP+GrzPOrDeeefwxaR9JOVbOW/5+OvE8tf5NyfF+y92xnJN284ll1q4835xcZyT9eijq7Yr6u0r7Ml77oizjs0/azUm7h+R6H0+My5zEZU76F8l41uqPlfplvMfKfJ+K64yi/ZDU3yWfR2XeD8l4jJV2UVkXpiT+QyWPoxLfOHN9iCuvm/HP9a1Wb+l1O1sPa8ePheO31n02Fq4fN5o397qPuPQ6x0bjgTgaN7jN86d+AgDg+cb+EwAAAADol/eiAAAAAAD3wftcANwfYNt5JR8xH+h9Hpv3APYtQNv55//P9xmnaPR8IW/oeHxDP5qafyHvEDcw7zHexrmLeWWeGxfEHc/zbG/96O0987W+H9L9Zdl+X/t7oKLTfD81H1qdt7FSP6LRvG11HYsrH2fr33MXZ/689n3g0v1prJTfsbG4by1/YuE86nV/5e+urlcckQf624LJeK7af/P2PuJw6/cp5gXyVlzgL6N8gOb2f+KN+IgzwFbWJesh8gjAegcAYJ/R2Tj/ANErB64= +1438613618.950,1.000,0.035,HISTggAAAc142u3dQW7CMBAAwKyTwCf6l76tUn/QF/VHPfZGD8DF0soIaIKTmUtE1Njxer0xINS3z6+PYfj+Hc7GyzHOh9Pp/WcAAB4TQrCrOMaL9RtPai8Wus/rsVSvx+p8aVx3qF5fTZfj3Ggv6z/rb6zaPyTXZa/HRj9j0u6Y9B/VOOt2D0m7c9Lu9fwx+bs5abeOe308JvM2JeMak3yYkvmYk/PRaHdq5E99/dDob0riVpLzQyMP6zzL8r0kx2x82XqLG9dvacS7tX6zfkoS99KoI626cmu9at1Xq27Gk+rkcOd9P/qciCQ/4852ii3NovuF2Pj4AOtUfAEAwD4Y+QKg/gEA2CcBAADgfSQA6rf4gLw2LrC+QB4DANjfgHUA1g3mDZbM67Cu1CPxg1XzXL6zp32F5wWvmAchTl3HKzq9j9hpHnp+Ik/kbZhfOpgX871OXM0nW9rvb+V+Qv5t4v78Lo49x83n1fftx9aqL71/b7b3/1O/VN6o/9uM01LvF+VPH3n4X/Ptexl1CVBf2E/8QjzkrfGsPh6/T1FfkMfGA/JbfPe5z5MfxveMdkOcus4vdQJQD5DPvErczJv4AAD2ObQVIQDUbehnvfwBo7kIog== +1438613619.950,1.000,0.043,HISTggAAAcF42u3dy1HDMBAAUK0/oQkKoAtqY4YO6Ipq6CAcwoERs5FxbE9iv3fxJCPJ0molyyc/v3+8lfL5Ui76n2tcLufz61cBAPgtNmo3ZvYjZvY37iResVG/Y2Y8p/a7Va9rlO+qa6s/dXvVufZPO0PSbp/0a0h+P1XtD1U7Y9J+X9Ubk/qn6v+6fFeVG6vfrfv2Sf0xKVePZ6ziMFblh6TekMzz1HJZ/PoknpG00yX97ZM4xMQ49hPz7JTkcdafmNifmDj+oZGn2bosjfUZjftm9bP1XJJxleQ+Q2Of6m7cn8tK++d/9+lbn1u3Pj+Wfg7j3Kj/4iFeAIDnvHgC2P8QHwAA5zgAAOceAAAA7zWA9QsAOG+AvAUAnCcA6x3kP/LPOMXNvIkX8g/zLk7X72N+wP611bjtN8fQyY9drWvzgrwwH3GQPFj7+132YfuV+BnX3H7IY/l0xLgt/d1O88i1+Y6F8mBqXsfOxp3p7mzdhfX/kHH2/dpt94O9xSvkwa7Hbf+WB8YpDrDE+xniwfp5I7/EyXgfbx2GfIHD5q/19ljxNl/yB/MK8tJ6Eg/AukfeAIDnGgDgfAAAANzJe8c3l5oHyg== +1438613620.950,1.000,0.027,HISTggAAAat42u3dS07DMBAAUNv5tFKvwF04GkLiBlyFg3EDWBQ2I41caApp8t4mapv4MzN20rLg4eX1uZS3Yzkbvo71fHj6eHwvAAB/qV55fr2TcS/dX71yfLXzfvvhuGJ7LTxvZteNSTut08/QuW5I3m/JefE4h2PrjLck1w/h+lP4fErOH8N1U+jnlIz7kMx7Cu1+n38MxzmcN4bPW9L+HPov4fXcyc+QvF86+c1ej0n7U9JfS+LVknaz17G9Q2d844V12Tp1VpL8xHVWOush2xd65196bJ11Pvxwn6q/3D+vbe9W/f3X/aysdBzIPwDuM+578g/Wi/EA2N8AAMDzLwAAALAtfqcA6wr2sK6sS7AfiBMA4P5vXAC4zwGw7v3Xfs891Ls6BXxPAqx7cRcH81l7nKp6Qpzlf6fzrTvPU1XvnmcA623D8ZEHsI4816srcVmftvB8l/7/p+ppmf62Gu+9/n7W+7yupP56v/fVG9e1OltHna4tT3Xn+wTyZ/6A/UB8QD35nqW+zFPc1SvriKt8WD9gnex7/v4eDupN/swL+RU35BeAre6/9RNg9wcX +1438613621.950,1.000,0.031,HISTggAAAc542u3dSU7DMBQAUA8pVJyBu3A0hMQNuAh7LsUNYFFWX1hOKUnq5r1N1DSD/fM9pI2Ux9e3l5Q+3tNJ/Vnm0+L56+kzAQD7ksNytHJf6/G2Ok9eaLvcyZfe5xLWl85+Jayfwvw1Nbbrnb+G48X1NayP5ayNZfz+EJaxPHc/y2PYbgqfS1j/ED7fd+oXzxvPd2yUd2rUawrlb+3XqkfpxLk28iCeP8YxzzxuLH9txGlq7HdotINpZn5Pje1beX9o1LsVx7ntL3Xyu3Tae2mct3bKUTv1rjP7hzSzfOf2g/nM7euF/frc/i+f2Y+vNa7nC79fa9wcbX5z6/OuIpTyAdCOkNfqAQBg/gLoNwAAML8EAAAAhuN3AEbKN/kKYDxQTgAAMO8FAADzYtBOEV9gH/2Q/gnjJvJD3o5U7yyubBC3pd8bMurznnkneaI9isuer+tf36skP7Qncbi8XlmeyGOGib/55TJxy8q/Sn4Zf2+z3Yz2O0OWP5vEOYu38ovHauWVJ+Kzh+vgPdug3zS+r3u/5H4ecV4+LuIEoD9E3iIfbimv/uv+Uvva1/3PtT+Xv1W/cS3Pu/ifXTt2/VxHkO+4TgD6UwCAX+cj35IAB7w= +1438613622.950,1.000,0.578,HISTggAAAcF42u3aQU7DMBAAQK+TOx/gLxy48C0kfsBH+UGRgFMly06a1saZOVC1Kom9u3Y2UZ8/Pt9TvL6lX8vfa/z8fbpcXr4SAMAMQgh2xSE6jysKr7X/v36/VI5bkgvfy5XzRuP4St9frz6vjX9tPO5SOF7eOI/cmL+1Mo5ojHvp/dI431T4PDfmq1a3ufKadtZD3riOciWOsTHuW9dvaZ3kTvtP3Hj+dFAc0s46SDcez/VOn6DvAgBA3wfYXwAAAAAAAAAAAAAAgKP53T8AoE+QFwAAAPc3oP4BANd1+TJe5At1gDwC9iVxQn7FSzwAANd/AAD0k6BOxBUAAP0egH0QAEA/hTwBAACA+18AANA/AwAA7jMAAAAA7svzDgAAfRGAfRvUHfIrvoB9BHkSN/NGfkFdi4O8mj/yKQ7IJ/3jHeIsrhPXQUw6b/v0OeMl7+IJvddB6ft5kHX3X/q9OFndPPp8+eDxxuT53DufOEn9zT5u/dBj4hvyfsr5jf68z33yGNfNsG6HyHeI913kxrhbB67roM6P7d/iQfG2/4xd16Mff2+deb5i/3S96n8/G4OOy3ro239Yt/YRxJdJ1vU3gkAGiQ== +1438613623.950,1.000,1.374,HISTggAAA0d42u3c2W0bMRAAUA3VRoCUknpShoF0kEbTgQPk+DFgrJbncPe9jxhGZIk7HJJDrqQvP36+PeL7r8dfz38/48+/X9/fv/3/HwAAYBcx+PG7XV/v141N23/VfrlbnAEAsN8DAAAAAAAAAAAAAAAAAACAq/F5egAAAADYh/M8AAAAAACAudyfAeMAjD/A+AXMOwBgvQLAugAYtwBgfQaMdwCwDgMA1nPAuAUAAHU2YLwC5gXAOEY/6yf0k+ultl/1917jQ3+Beebuin4BzPPmqxNxCPFE/5O0X0O8jFPAeAbgsutAaDcN8Qx5gfEwPR9DfEkQd/0oHleMZyR7vH5iZf/cNb/ltf65cl7L72vn7V0/PySv1+ZPGG9cML/jpuNIXl8jr2PT/Fx9ju7c9B51SGl8/rufs8w+N43B/XG3dUBdkTsed+23UfXMLvPJ7t/7EI15PrtOy36unKUebW3HLp+jj8VxtE7uFZdV59Cr8yga2xUXy4PR88nsPLv7+8Vr4z27flrVb1natev3LcSm+V3bjnLzOicm91PveMcmeb37uNwlb+MgT3fbx8+ul6PxcWVyu2vbvzo/e7WzNP7d7Dow2zhbFbfZ69gu81Gvfc6qvFp1Lrrr9cbF4h2LXi8m9UfpFI9Z97l671OO4j07H2fVGb3yP9v9idH91VpPj77esvjva19n1uuerf8+jrvyye9Z9+3ReN2z23c0H5fF467Xuh2VeZ9lX/tYnB9H++bavO+VL+VkHmT7nNGr8VtVr3z0fHG9qb3/Oeo6zs5nrfX6rDyLkz+zz3erzyWzvA981vlatjpq1P6ldp6MQf03eh9/1K7SuJ7Ozq+SJJ97t3vUfD06Xs/O+42zdc+q84le90lHrYu17Z41P0dj3jw658eo+0tZ7j/3qidbja5PysnXffX6Zp0blEXzyKx8yXZ+92qcyqB2jlrf4+R4iMn5vZvSOE+svk/b61w+y34vWz5le990bd2w6n02vT5HPmvdqb2P3Xp/Jev3f8Tg8Xs2L0tlvF7Nq+fguGTZH/W6vtb6u3X/uPu+6TH4eWuf/+z+KSrrz+icP73iUXtO2jo/rnof0Oq6bNTnEHbdl2SpZ7M8f/wGkp0I0A== +1438613624.950,1.000,1.128,HISTggAAArN42u3dbU7bMBgA4LxOCj3EJI7CXXaCXWESN9hFd4MiMfhjyXNa2sR2nucHVaFN7ffDTqigP97+/J7i56/pn/nzNj6+vlwur38nAIAtRCOvH42Mp9W4leJT+n668rjzynykym1pXF/HP33eLpXjRHb/VLjNH7esfL1T9riU3f96/FPleKfC9yN7/nPheU+F+Zwr48+f95yN85w9LlXmm8+v9POlcL9UT3MlfqXjl/orFfId2f2lUNdzFpfSeGt1uVT6pXSbbuynqfL6c6UPp8I8ppWvV5pH7fjzndfLdOO6ee3P48bjP2pfaH1fjAc/r5e4xjcfH43kv5fzMOQNsN6IKwAAzvfMC/QJAAAAuH4FAHAeB/pAvwDgPAsAAOD+1z2ujwAes76CPkFckUfQN6hP81EXiDvoG30NAC3tc/ZX9B2gb9qbV3Qeb+vrWHFs5fMDt/68wdTYeFqpl9hovNaRdXGJjetKXrilHnpZ35KUbhLXvT6Xd6t92zrJyNdT6vuY8ZR3cTd/+VFn7JmHUA/IH8Bhfff3tb2+v2Ff6yPO0cj41LV5iY95wR716n1SaKfu9R3qCPSReMkP6Hvk8bpxqkfUAehH64L8A/oV9J++to6Kd8t5Uy8AHGG/kxeg937U7wDYH+SDY9VPqEdg0P62PokL6GfkAfSn8ZindRt9hPirL8B6Ie7igzzQb369X3nMeVln2KM+9q7rkB91bdzipK6N27wZ9Lyp1/9roi/UuzjRS55Gu95Up+p2xHpQ18eKj3zT83WQcboeXTOuGCzuMXgdRCPxP8r+mDbuP59/Yh8V53HHee99xN95IR/HjXOIKzvEWx4A7KP0F1+/3zE+rEOA9QDUsXFbN0BfGrc6QZ30Iok3+hLnVYA+Q50YN6hvAOs3Q+cr1Js++V883gFSvQhX +1438613625.950,1.000,0.055,HISTggAAAdR42u3dXU7CQBAA4M6WgolX8C5eTRNv4Nm8hzfAGPBlk8kWgf7Q73tphLo/s9PZBR98+fh877qvt+6kP1/jdDkeX787AGAbYqbfD6GfNR63XvcY2W5fXVvtluT+SN7P+i/JtR53Pb6/+4bGOA7n6666P/t5l4y37ndfvT9Urx+ScQ5JXIZknkPVzlMjbs9Jf/tGHKMRlz6JU0niVrffJe1HNb5IxpXlUzTyp2+Mt/V7XdJvaYwje16isX598vrYduLC/lvPZZb/uwv7z+pRjKxbl9aVsXXx2jpcVrq/lpHjmmu8ceX6Xbr/3SpvHv18MnW/zqPA2uuMOgYAAADL5/M7AOD8AwAAAMAv35sAAAA+54D89xwCAGs5LzhnAIDzAeD5Ej8eKY/kF/esT2v7/xL2BYBt1xn11bqYj+fAeogDyFPrAvLVfO4xzxC/RaxH3Hndpl5febLuvCz/zMt7jSesk7hsYL4x8/0hbzedt7HR+ft702PXvVudu/Fciqf5bGX+YX0mnbd6p77ID8C+DrD881Rc2e5U37uXG/W/9M83a9/f7K/zxFfc58nfqeNeFrJPgLqAfQ7UK3Ha5vzkmTgij0D+AgCAczAAAAAArE38ADpBCCQ= +1438613626.950,1.000,0.040,HISTggAAAap42u3cS07DMBAA0IyTFLgDd+FsSNyAO3EeluzKoi0Li5HTFpo0eW9j9ZP4O2OnSDy/vb923cdXd9AfyzgU+/3LZwcArFPc2X2XUm/c6Tz+1ffizO+1rqtfl+Tz0ij76rrh9/Ptz+u+uv70+Vi9PyTty+ofk/tF8v0haVdXXX8qHxr9ezyWT0m9Q9K+PmnX0Hh/1+jXmJT1+I9V+0vS37resTFvYzJfYzKvU/sXyfjW7crus0va1zfum8VHPzHuSrLuSxLHZeK6yNZXFjfdxH5fm3+iEa+RjMe1ebFcmCf7M/P0pfvRuddfW89S99eYqV6ArT4nAgAAAAC0+H0SAAAAAAAAAJbP3/cBlpEPw/iBdYR1AQA4v4B1ywbnPYyv9opD8wrYF0A8gfU3Q3/EJ/ID5m2e9sWN+x/Wv/xgPGfvj3XFEvYRnEO2nH/FzX2vN+fZdeSHqe0oxk/+31A+W9u5c67xLP/UrhB38tINxsN4AfKqeTTvAIDzqfYjTvA8ttR1aT0jP4iDteyfW/9/B7e6n30XAADW/3wFgH0AQN4DAGCuc118A4AaCKg= +1438613627.950,1.000,0.034,HISTggAAAY942u3dwVHDMBAAQF0kJ03QC1/aYoYOqIiO6AAeST6auZExTkzw7keT2JZl3Z2l8OHp7f21lI+XclYvbZybr6/nzwIA8BPhOWedH3/0eWLh+GPl/q8OXXs9ryXX15nXte54Ta7P+suui6St3X2vn0+Xduq+n7r7HJPv+3774y0ZX0vaqRtXS+ahJeNryfPU5DnqIH5tEJdpEJd+XKfBfdvMeY1BP3VwXUvO7/s7JJ+z+Edyn6xes/5rEueSfG6Dej8mcSyDcS09PmpL8vxz31ujOl86nq3W31i5v73sH5bGcS/7JACsmwB4fwMA1n8ArAcAAPZfAID13z4GAOuw9Q6wTwfUH/IA1BeoBwCwzgHgPQ3gfYo8Au5Rp2EeUCfyDfkO8kjcbtCPfLH/B6y7az5PiIc6kN+Y54d02Dguv/0/pbHTfA317P1yh3lZ6+8M5l8+PUJ9xk7qFOuK+Qd5vMX4rfeoQ/l3q3kM8UBcxcE8gPoCeQPqCgCwDwD1AvLY/LBhXOQBAAD8g98R37l0B9E= +1438613628.950,1.000,0.033,HISTggAAAbV42u3Zy03DQBAAUM/aSZBogQOdUBsSHdAQJdFBOAQuK43WCY7jz3sXKybez+zsx+Tl4/O9675eu4v+9xqXy/n89t0BAPxHzPzco8pdWnxj4ufHfq/ceL8ur2/8vTTaVZ1ru6G6/t0/VOUdq+eHpN5Dcj1Vz5WkHXU/D8nn0qi/JO0fqvKeq8+npL1Dox3H6v5T9f26/JLE85DUW5I41fVG0t4sPtn3Imlndj028rCv4pKNU5ZfQ2M8hkbeZ3kXjbj1SXkluWb1ZfM0e/7adau1DkWjH1Otk3Hl/VvLn3pdH9v+W9u31v04dlav/uC9BAAAAAAAAICM31XB/AcAwLkQALD/AgAAAN7nkb8YB4Atr6vWYfsiYD0A+bysfpv/gPXZOjlH/MRRfuv//uJt3q8rrsYL+TNdf8I47ar91s99jMNcv4eGfF31Or62c9rYckvHmPjZD9ah3Hm+xE7yfWn759bGLXZW/9zr6aP/Xx0rqycW0q7YaDyWnn9bmyfOa+x5f8d4wFLn29LOG86NAODcDY/Ma/NGfJfcb/kpD/QX5C3IU+QzxhnjDQAAANz1/f4HvckHxg== +1438613629.950,1.000,0.024,HISTggAAAZV42u3cy03DQBAA0N21HUfUQC+UhpDogD6og3LoAA4hlxEjm1ghtvPeZRUbj3dnv8mBx9e3l1LeP8pJ91PWU/H89fRZAAC4nrrw/tzn6sL6TF2vF9ajhevdzPjxegtlH+J14X6XvP9cDsn9GL8lz53LQ1LvManPGOL3Id4xxOuSv4/tP4T3HcPnkrTjmLS3hLhjeO78+SHEGZL6DEl7DqHskjxPxYnjZ5jo12z89Mn4jfXqk3HWfv/elfZ3myiz9/Uz52tN+rdNxKsT7RmSvHdJ/rK4mTZzfWoz17/2x3WtTNS7Xbj+Ll33y8z8rXW/W3v8e8mj8xbAvtdB6x3OMQCA/R7jCwAA50AAAAAAANg2v5+D+Sg/YBwCAAD43gSY3/qV+8mTfpd37Yd9ziPzzDqG/pUPgH2sZ/7vM+bPdutt3gL2C+1f03vsS84dwG3mXb3zvNSN9Zd19Tr5Xcs+WTeav1tp8rCo3tYR1jxenWPQ/2C+YNwAYJ/4j/fY78B8wn5gnMsHYD7LhzzoTwDY2b73De+EB9E= +1438613630.950,1.000,0.027,HISTggAAAXF42u3aS07DMBAAUE+ctHAHDsKOoyEkbsBZuBc3gEXJxtLICSW0oe9tLKWxHY/HHyQeXt9eSnl/LCf1u4xT8fz59FEAAMjFhetfyziHhfVjYf0hqV+b3+dyTJ4399syJc+HpP2237Hz/lwemt/n8i5p79iUY/K9bf2p097QvDc/v0/GMTXt1E5cp874D0m9YxKfqRlnmwc1Ge+YzM+UxKWdn7JyHockTpHkVax8b2k+1k67pdNO6fQbnfUdC/utnX7HTnvDwn3i3H2rLqwfK9+LX95vnVv7PEf/+nv3FpdbvQcCgPNIfAEAwL0XAAAAAAAAAAAA1vF/b+ID2CcAcA5hHpE3xg2A80Icri2+Yb5A3pgf5AMA4LzHvIL15PuuMi7iJf/WfL98EQfzJn+4TB7Exv2E9WD/uqH+3K+3GX9Yj5v2FzsbV9xYXjiPQP6A9QEAwN7vk/FPxvHT/tzLAfsBYJ8DAAAAf/ee2058AdpDByc= +1438613631.950,1.000,0.033,HISTggAAAZB42u3dUU6DQBAA0J0FWr2DB/AWns3EG3ghj+QN6kf1Z5PJ0opIy3s/pHQZhmV2ComJT2/vr6V8PJez4Xsb583p9PJZAABYX9zYebI4zfNluq2deGMyfkg+H5vjaxOnJvFLEm9qzv/QfD8mn4/NdkrGtfHb62jz/hn/2Mn7mMSZknHZ/na+Dp15jSTu2Ll/7feHJk505j06cYdOPrVzXE3yH5t82zyy46MzPjr5RWf8kNTNkKyrqbM+567XIdmfHTcm++vMPlM7+9t56sWbu79e2Sez7bV9em6ccuG8ll9e33/9Pi2dh+cH1w3qGUCfBdCXAAAAAAAAAIBL+HsA8wsAAOD9CAAA8B4CWH+g7t0/ULdw7/Vo3ZhX8wKgz6E+tpRvWF+Avo86WO286vQ26yF2Vt/qFP3fdSydd2wkH/3tvut5reex0Bd2XW9/9X9iY2frdet9f6k+sdX3iLjx9bv2Ogr3CzyPA/oZ7LiurAN1BgB4bpA/3Fe9WjcA+H1AfeI+APoNAAAAi7/PfQEULQfB +1438613632.950,1.000,0.022,HISTggAAAXR42u3d0U3DMBAA0NixG8QM7MJoCIkN2IJpGIUN4KPl58A6hVYlVd77iZTE8eWcu6Tig4eX1+dpenufjubTthw3T5+PHxMAwBaVnc371/guFW8N16vJ+W1lXHVwvP7+nfrj/Bjf9/mHcLyG+EbjehjfwvE2iKuFcfF6fRDPctreh3Fxvh7234X5erheG8Qb87qE/T2Zdwnzx3nnJD8tub9Dsn7zYH1G583JepdkXE/WZU7yWwd10cP8ZbC/DtapJvXVBtepyXkluZ+s/4zG16R+6+A6bWXfm5O4ysr+VM58H5Qz+3G50H7fB/IlL6AeAPUN6DsAgO8GAAAAwO9vAAAAAAAAAIj8HR0A7ylQR4A+gfUErldvRd2jXriRvPt/INuMWz3Jj3zJO7edV+vveXOfwH/Up3oG9Ev90PsCrlsXW+k/5crz6yPn5amoJ/Fu8PlS19zC+0dfBM+jfLpPAAAAYJ+/44u4drXe8ggAK95jX0XwB9A= +1438613633.950,1.000,0.032,HISTggAAAXh42u3Z3U3DMBAAYF9+oIgV2IXREBIbMBArsQE8lL4cOrktaRXS73ux2trJ5XyOm/bp7f21tY9d2xt/2tg3L1/Pnw0AuC0hBZuYt1ho3uPMftX704nHP/Qf0ueH13Mxbkz98vj71G9K7a44z5TGT+k4cyfOoThPjiPSuLvUVnEPxfVH6jcX4+Y0fizin4v4HtL5xk7+dp3r68VZze9jkaehyFP+vBXXUbVV3VV10bvO9Fz2qx2K+Kaif153Q1EPYzEu12Mr+rcj425Hvt873qn948T7z7n3u6ou44/77KXGrW3fjyuN79XN0vup73uA9Q4AALDO5x7PTXA76x0AAAAAAIDL8D8OAAAAAABsj9//UWegrgHrHsD91XXJn3nGOlH/qBt1D4D9GPOJOmG982YerU+2m98Qz1Xis97WnbeQn1XEGfL9r+Kxz91GnPYv+y/rz+/Wn9/Vo3kErrOO/N4BAAD4vg/WjfsCAPYbAAAAAACWEt+RQwci +1438613634.950,1.000,0.023,HISTggAAAYR42u3aS07DMBAA0EyctsAZOAg7joaQuAHX4HDcABYlLCyN3BYL5fPexmpwbGdsT5KWx7f312H4eBrOyk8Z5+Ll6/lzAADWJYRgEfGKTv3FlX+PG9u99Lyxqh83XndcWX+qjlfPrb/japXzeadGu3N5qMZxqurfVe3U541Jf3O9Y9J/Pa6pavdYlVPj+KH6/JAcL0k/8+f7qn7Wb33+WB0vVfwiie+hEddjMm/ZPE/JuMYk3tl1TY15m5J1WpL+StJOSeIwXrgvS2PfRXLdrfZKYx+PyTj/mjcuzV+t8/4774+d8nx0Hl8kebT3fXJpzyXRebxrfe6KnVwPALif0GtdWB8AAAAAbInvuwAAAAAAAFgSv18BAOB51vUCAADeC4D17nN5QX4H1rWvwngAkJfFc6fzb30j7yCe+4yDeVx23H1PAdvZ5/bPvvOO+d9GPMM6gcXvs9jYeLf6PhAbmT/2mYesG8/VAMjL5ose8d/L/89ad1gnAAAA3rMA2Pl96RscPAdN +1438613635.950,1.000,0.028,HISTggAAAZJ42u3dS07DMBAA0NhxPwuOwF04GkLiBtyFc3EDWJQsGDEYStNP8t7GUjKN7fGnjlSp988vT8Pw+jYcjJ9lORSP7w/THQCAr8ofrw8nil9Kni7d/zJTP2JcTeKn6y35fAtxNYmL5diJ34RyDOUmqTe2N8ZP9uF+C3EtaW9W1qS+lpTTc3dJf6dyG8r4nH2n/TV8/q7TzjHEZ/nZJvH7JK4l+Rq+f79J81qS8Y/jEed77dwfkuf05uuYzL9tZ53VJP9ZXLy+66zP3nrO1mNL2l06+8fYqXfu76vyy3bOtT+XG/u+LOpb1bln6ec3MP/B+gAA1n0uca4BAAAAAAAAAIDr5fc9AIDzCwD2cwAAAPC+jHEDsO+C+S5fxz3XOAEc1AvX7/8vwPlOvuQd7EMYT3n8b7vrwvNZbny8ysrmo31PP0+xTq41X9bvPPXM/b8y5+p/Xcg8KitZF8436xrfutD8Xss503vrvHmVX7AvYR6D+QjWHcYVsE7lE3n/uX/mG9Yp3PY8sy4AAADO9P71AXxlB/8= +1438613636.950,1.000,0.019,HISTggAAAYt42u3dUVKDMBAAUEKg9RDexaM5zngDz+D9vIF+2H64zhpoTQfxvR+mbQhhsyyhP9w/vzwNw+twUk/b8rl5fH94GwAA+K50bp/tXzZ2ntnvdeHn8/5j4/usn1a7ceE2xjcbx5T0PyX9HkL7uN8c9j80tiW0n0I/xyQ+WfvY//n7u/B5TsZbQ/tDIz7H8HtsH88ji3tN4jwl45xX5kVNxjUmeTKGOJQkX1vHqcl+cX6yvJyT45VG3IdG/tdwnFZ9GpPrcmjEozWOa+tuacSnrKx/48rx3ap+97pfXHqepVMc1vZbfrm/ra87APb2PAUAAIDnKwAAAOAr/wsAAID1NKAuAOC+Ix4g750HAADWhQAAYP1tvQ6en5EPALh/sM95MO994iCufeJRxOtPjEv+yyPjd12gbt+if9eVuMkL+co25mXpezh7H790yjd5uCwOe3sfp3kXv5/Oa2vvoy0bi/tW/t++dJ7UD/XIuNwfQL0TF5DX7HW+5A+gDqu/sPW8dr3IE+Cf1YkPFFwHCw== +1438613637.950,1.000,0.021,HISTggAAAXN42u3ZQU7DMBAAQG+ShvIH/sLTEBI/4BX8jiM3OBQhsWKVpkVp08xcrDSxY6/XTto+vLw+t/b20Q767zIOxdPn43sDAM4TQnBV8Y4z2w15+Ut3ZLtRHHep3KXzw9/vqT/HfWq3T+0N6TjXq9of0+d3Rf3qPvtifEPRTnWfVrST+7+baLcqx6L+VLyq+cr93xX3q/rfFdfn8Y/pfl063hf9HIt5uC/yLs/bmOZlODGfcr+qeRyKccbEfVtxfRyZV9X5XH+cOJ/jFCeWbea45l4fM/ex6nx/Zv2Y2Z9beX+ZO95Y6TgBYEs6IQDgCvn+h/wDAPDeBAAAAAAAACzP/4BgnQPrWXfWM3iuAgAAAGR+r0B+wLbWl3UtX4xP/AH7qHiZB+8blx9/WH8gf8ULADzvuOi8hHxcRb/ixOtjI+s1Nr6u/2uel45jt9C8e07ddr6bZ+BS71sAvicZJ2DfQNzFEbC/iANYByDvAVa3730B39oIBA== +1438613638.950,1.000,0.044,HISTggAAAX542u3YyVHDMBQAUH3LWeiBXqiNGTqgLJqhg3BIfNHMRw4JiZ28d9GICK1fy/j14/O9lK99OaqnNI7J4fD2XQAALhELqT+edH7/Oj/RKRdJueGUbpp8m45NvjTv0aHTTu3UWzvp2KSRlNsm+dLkN03a60ck5Uvz96n8LmmvJvlsnJvk921S3y75v2m+9814hqa+KX1J+t/Wk8VLbcpl/a6d9axJHNVk3bL4yOJ4SOajJvE8dtrN6iudemNmuZL0pyTtxpm/9/p37vlUO+WGmefZ3HaHC8/ja91Pl95fceX67nX/x43aedT30rO9j7yvxc8tztf/noewz1c1HvcR1h+WGZfiGgC8IwDA/WOeQVwBAAAAwLL4HgcA7mnA+QEAAACwVr6DLGsdrAc83rlpXyMeAOeBdcN8Im4B+9l5gn0D9g+ID7Cv9YtbrFuYJ6wX1veu4xbf646LWNn4QvwAAOCdhjgD7FfEB4BzBwAAAAB+4XsZAAAAAPAo4gddtQec +1438613639.950,1.000,0.021,HISTggAAAX542u3dS07DMBAA0Ex+rTgDd+FoCIkbcAmWHI0bwKLtZsooQW0JTd7bWLFj1+Nfomz6+Pr20jTvH81Bd0zjkDx/PX02AACci4Xajxv1L4rrmHlfVS+Xdz+/d56Vtym/Ldpti3qn6/6YDkV+P9FOO9FOTsdUb0zlD8d0V8R1yt8X6ZDq5f7si/iGIt0Vce5T/8fid9qZ7ef8sehnHs9mIo5uYh6bIj+K+e6L+RtSeRT974t2h1Qvj+dQ3NdOXGfjzPur8rGII4p9kdsdJvZrdW60v/ydql73R+fzped2zKx36Tlfna/XjnOp8V7Le0DcuP3/Fhc4FzBvWEeA/QWA5wUAAAAAADDF938AAAAA4F74ngkAAADAkkK/wL4Wr/E13+IFwPMCWOX+i5XHB1tep/Yn1uP647XPjaNx4rfzFdbZXfYnbtwf+5s1rIdrn3v39j+bsZF16H9XndtLjotxB/tKnObDvAJ4XgIAbP39yXsb4JzEvBhHNrruvgHaiwfV +1438613640.950,1.000,0.023,HISTggAAAXJ42u3W3U3DMBAA4NhxWmAGdmE0hMQGrMFQjMAG8FCK0ImTW1BLXL7vJXL+fLbPl9w+Pj1M0/PLtDN/HMvucP929zoBAJxD+eX1Ucc1WtzlwOu1c76G8+3AflryfP3+f/azHY81ua+F9t51eK4l711C+yqJexPa+/u2yXuWZJzbEF9LnltCv63Tb036y87H98Z+W5jvmySuuTPPcd22nXyqyXhbMt4lyYdNyIcl6acm1+dOnpYkH2tn/Utn39RO/sc8mpL1ys7HfT4n7dLZd1PnuV4dKgfWj9iuyfHYevdT9Uz1+tg6fqo4L+X76D/DuLH+o43bfgHUa+QRAAAAAAAAAAAAAAAAAAAA8FUxLgB1C+CEdaoMHj/4n7CvzAPywXqKy/r9ZVzWG8apG/ar+UO+ym95sOb6VwZ/P8Cl1if1c93zVgeLWz7xH/e1vL/s+qFuGr/5ZdR5tf6AOoN1BgAA/N+bB0DdAcD3Z5XxvQPulQfz +1438613641.950,1.000,0.015,HISTggAAAVx42u3aS07DMBAA0NjOT5yBu/QqXAWJG7DlkNwAFqWbgZHTKgLavreJmiYejz/TROrjy+vzMLw9DUft61iOh8PH4X0AAOB2lJ2vH8N15efnym/nT8eaxGmdfsT7s3hDEn8O55dOf09x1pB3zH8KcWsSd0ruW0N/lvD9HNp7SPKI1y+hvy3Jcw2fY56xvZbEXZPzcRzGzrqI30+dz2Ny/5jEjesvzmcNcaaN67km7QxJu3MnrzlpP9uHrTPPpTO+pXNsST5Dcr509mftXF87+deN9aptrG+l0245s16WjeN1aZ3e+/6927nW+PfW773yK2deBwCc/zt6788jAMDfvfcDqIMAgN9bAAAAAAAAAAC28D8RQP0BQJ0G+wzsA0AdUb8Az0cAAADgfdU4mncAddA4mTeud97qTvNtfXDJuijWl7punn9tnIwv9gPm57bztR8AAOAfvy98Al3VBx0= +1438613642.950,1.000,0.017,HISTggAAAVp42u3a0U3DMBAA0JydtHQHdmE0hMQGLMBIjMIG8NGWjxMnp1T9SHnvJyq1Y/t854aqj69vL9P0/jEd9dM1jpfnr6fPCQC4DyEEm45b3GhebeV4c2ofg/u1ov+Unjt70b9ab//9ufWnX0/jzcW8qvWc/74/XZfBuLui31zMJ4p55Pmc73M4XR+K++9SvyW166ndoWjfUrtejLMMXufxdmndo/i04j77wT7n/GhpvfsiD1oxn1a8jrTuZeV62iBv5+L9Ub1GMU6sPD9y+xiME4M67yvPl0vPubby/Ivi/XZhPGMwn7jyfB2t76/n/LXzvfXnlOcQcUA+wz3kkbwHdQQ4ZwAAAAAAAAAAgPvjd0cAAAAAwH/h+1AA8LkOqB8A5y/2UzwB9ScOyGu2Hlf7C+rPOuQhyL9t1kOoa9SLeIkT9lu+qaNNxtk+AQCeJ0H+AQAAG/7/5Rs/UQe6 +1438613643.950,1.000,0.015,HISTggAAAVh42u3aS07DMBAA0IzdNnyuwF16NqQu2LPlkNwAFqWbgZHVFPoR722iRHE8HsefSHnavT5P09vLtNe/jrE/bD+27xMAALU4Uz1tYVxtEGe+virKtSKeSMfVke2Jop4+KNdSfYf714Pn9VRuk84P5eaiXE/PnVO9q8H5YzrfpPP7dP0h1RPpvqqedSq3SfHPRR6rduV4c97moj9yXLn/qutRxNl+/m75lseqv6Io34r3IAbxVvdHEffBXZGvaRBnFO3qg3HSB+OnGmej9vVB/7Ujx/905Hy1dH6OX6p/aZxL141T15tzrVeXXv9OzcO15elW+w3w3QMYP/obAAAAAAAAAAAAAPgr/tsFAMC+FgDAfsP+Sn4B4xcAAOzTATAvW1fAvKX9yAveDzBe5J/byGfcWL/7/w79efn3Iv7J+xzG91W3M8yTYF4AME8BvhsBgEvvBz4By6kHPg== +1438613644.950,1.000,0.031,HISTggAAAW142u3YS07DMBAA0Dixk3IH7sLREBI34CLsuRQ3gEVTFiONHNQPJXpvYyV1bHc8dtw+vr69DMPH+3A0rWU5Fs9fT58DALBv5Y/7KeJ01fGNF263dz0m/W6tl40/nFN/ruP9U7kkz4+h3mEt5/D56fm6li25H8exhPolXB+SdsdwvyZxaqF8CP0OYTxzqFdD/SmMK4vvlPTfkvjE52qn3ZqMp3XqtSSOWd5kcV+Sdqdk3rK4lM78ZeuiJfGvST7HfkpnPdTkflZ/TsZZkvU8ddbZuev/t/vUtHG/2tpu2Tiucub+eu5+7H3oe4mz+ZcfmF/gP61L+wJYDwAAAAC35n8YAAAA/P4EAO9BAAAAAOA2/F8IYL8GsE+Jv/GLFwCw7/dxcY4A+4o47PJ7yxvuKW/lI3gPgPw0D5gneWe+xMX5VHyxrrlk3It8wDrBvAAAzhvOKQD2R8A+Ig7IE/kKADhHAQAAwH3/7v0Ga8QH1Q== +1438613645.950,1.000,0.114,HISTggAAAZN42u3ZQVLCMBQA0CYpBe7gXbyWW2e8gRd15xIX1M2f+ZPCILbw3sJMoUnTJP+nxZePz/dh+H4bztpclvPf0+n1awAAYLly5XllZf3v9ad2jnvt1PB9TeqVpF48vyX1fj8/zOUY6rek3V0oD+G4hPZ24biF42kuj8l1p1DG67cwDvukHy3pVwvtj6E8hv7EfpdOv+P196Eck/GdkvZq0u4UjrP5a8l8tc71SjKOWf9r0p+WzE/tlKVzf1PSbhY3MS6nTvxl6yjLV63zfe3ki6X55tLzhs44lIV5uHfdtjCv1hvvJ5d+/lf7W7my/lr2u7U9FwAAwDPz3AzIKwAAAAAAAHB7/m9mHAAAAID/5XcJAAAAvA8DgP0Bnj1exD/WF+YfxBEAt83P8jfIB2DfQf4yHlhH1jNbXK/Wz2ONh/mEx49LcY79BfMGyE/uA/GjnwAAcL/nU8/h3g8A+wRY52xh3swz4sL9uC8A5HsAsA8D4goAAMB7GABgXwZxDQD2QbDuQPyAdQcgTwMAbPi56AdW+ghE +1438613646.950,1.000,0.026,HISTggAAAW942u3dS07DMBAA0IyTpkhcgbtwLXYIiRtwFC7GDWDRdjPS4BRQmpb3NlYT7Njjz6Sw4OH17WUY3p+Gg/FYxqF4/nz8GACAdYUQiPMVxC0W3h+L8nS/Hctd+ly104r6ud2p6NfU6Ue+vy+ePxb17orn7VK9qaifP89Fv3rtzcXzpzSuKNrZpXZyHOZUVtf3Rf9bsS5O1++L63MRnyHFf1y4rqZi/LFwnuZiPub0c60Y99RZr9HpXyxc53lec73WWV+t2O/VfozOfhyKfRhFe1W91jnXe3Faeq5V58/4R/kkFvZj7XwWG21v7fztfWEb8TAPWC8AAAAAAMBP+LsBAAAAAN/x+yMAAPCeDwCA9y4A5BnxNj4A5w/mF/NsvgCcI+IA9gPY74gz5hVwPogbcKl9HVfW33PrO9+4hfxqHcPl8wnn5V15eVvr7Vb+D3OY3389LucKAPIyAOD9A7CvxQuw38QNAACAX30f/AIEVweM +1438613647.950,1.000,0.024,HISTggAAAWR42u3a3U3DMBAAYP8kjRArsAsrsQFCYgP2YDY2gIfCy0knB2irpvm+FysGuz7b55iKh9e3l1Len8pR/y7rsXj+fPwoAAC3oG5snO3EcdZfPpdwP8z6i+PNnkuo7+F5SuKug/GE+2s5hP7m5HPnUC6h3SHpdw71UxJP7P+Q1E9Jf/eh3RLKltTH8f+4S+Z7Ttq1Qb9TKEtS35JyGuyHnszLkrRfQrs+2I/x97Jx1yTOnsxfS/Kjrlz3Nsi3LL6e7MdR3vRkPDVZt7oyzr+eu3XlebNWO9PnjeL973ycat5O9XPve+DS+SWP7TMAwH0A7A8AAAAAAAAAYOv8nwMAgHsc2113+wAAAPy9BwAAAMD/+D7mOua/Wnd2fE7Yj/If5NFlx+MccH5aBwDY1nvG+4895VfdaB7IU64hT/b2nm07i/dW46zynSuYr2re7C8AAPcPQN4CAAAAwFn4fo1N798vliAHZw== +1438613648.950,1.000,0.016,HISTggAAAVl42u3dyU3DQBQAUP8ZL0i0QC+pDYkOKCAt0gEcgi8DX2NCQAa/dxkl9p/Ns8WXPDw9Pw7D+WW4qO9pXJLT62m9AgDwNXHj+36qPnGw/l6VJi6S/GoS1ys/krjS5Dsl9RiTeq5xc3J9bPKdkzSa+DmJb8u9a+KW5vPY5Lckn2tS3pjct5Z732nn0nw/Nd8vSXtL0q/l898JH/KNpNyxiV+Sfq1JOXMyXqbk+pLk1xsvtZOWJI1kXpRk/rTzoib9MXauR6deQ5NPJHHRWT967e2VXzeuD9n6EleuN7GxfVvv3xpfvrnflCvLvXYf2Mv+F3+0/P9yfgAAOPp5UHsA8x4AAAC4Ne8LAAAAAIC98d4SAADnUgAAAACA/fMeFQD7CmDdQv/qP/0CAPZP+zE4B6u//tRePF88H8/xN8eB/3M99u866wtHmpfGOwCAcyEYfwBAup+/AYHDB64= +1438613649.950,1.000,0.017,HISTggAAAUB42u3aW06EMBQAUFoozsQtuBeXZkzcgStwFy7LHejHOD83uamRQICc89NAH/RdmMzT2/vrMHx8Djfjb1huwcv389cAALBEOUi+tcoZdl6f0im//LOcksTXP8bfTeH+FN5b5yRdjL+HLUnXwnPHpNyWXF9DvocQX5L4KYTxfgvXl6TdU1LvXj+MoT3XkO6S1K8l148h/Zz0c9b/LRnvIemPkuSbk/aNSRjHuSb5s3ncOuWPnXHt1bN2xjlbP711WZN8tRMufX7daH/s7Te9/ar3nLLRPrzVuVBWqufW5+Dezu+z1RfjCgB4zwAAAHwXAAAAAAAAAACwX/5fAnDOfd3+DgAAACzl9wXMcwBwvsFW8828BQDAe7N+AADnhvED60H74MjzvJx8nRy9HfVg/WV/xfrXLgAAAABgfeUHJPcHsw== +1438613650.950,1.000,0.027,HISTggAAAV142u3Zy03DQBAAUI/XSQg10AulISQ6oBUKowM4EF9WGg0Riu2Y9y6rJOP9zH6ySp7e3l+H4WO4aJcyfoqXr+fPAQBYVtxZveZl2XZioX5Xz41d3NS9ji5uTJ7r35+Sco4/FO21JG5M4oZkHFMXd+zKQ1HOceekX2Myjiwfp6T+ud7HLm5M4h66elrSz769czHe6Zf9PiXttyTv0fW7z0/WflZ/dP1oxTodi3qPyetqH7Rkn03FPqnWSxXfkvWe7f8onq+0K8+XSD5vyTir5/56/vX5rNq71fm79vd3bLyd2Eie1pqXWHne/kt7YF0CzhEAAPcuAAD3HAAAAABgq/w+aL4BAHC/018A5xsAAAAAAAD7538t8wQA4N4iX4DzBKxD+2vveQj5wzztNh/m7bo8hbwuMr6t1R872X9r59V5g+8vwDkEgHMc6w0AAACAe+V3HgCAG9+3vgFidgcb +1438613651.950,1.000,0.021,HISTggAAAUd42u3ZS07DMBAAUI+TNu0duAtbboWQuAGX4HjcABalEhoxcvmpDX1vY8WNx/FvEqk3j08PrT3ftYPpvYxDcf96+9IAAD6KC4tzrv7in69bnNg+Tow/ff692Xqq36Z2PZVzuj7G2aTfI8Xt6b5juSueayri5/pt0f88iD8X85LHt0/9zIPrpfg9x98U4+rpuffpvqVYj21qtynWvw/Wqxf3L8V9vaifU/+7In4U+y/XL0W/vZjXqZifzeB5o9h3rWgXg/MyDa7jxHNZ7c/RuWyD+ij2SW7XfjkfVfG/midH4730/B9n7n/t79m1fQcAAAAAAAAA/Ff+vwUAAAAAAAAAAAAArlU3BQDAhYqVx4drOEfAes+p/CBPA8h/AKw9L3uf2H8A4D0IzsV324V5NU7j4I/WzfrZ94B8AQAAAAAA8BPxBlMoBzc= +1438613652.950,1.000,0.029,HISTggAAAWh42u3dXU7CQBAA4O52i2A8gnfxaMbEG3gYPZY30AfAhwmTRTC2Kd/30mzZn+7sdCkkhMfXt5dheP8Y9sbDsewPz19PnwMAsE6lU+6dH66sX24sjpf2e+m6lDP7++04NWnXaz912o2nn0d/xptCOdbPrncM7TfJuLFck/N3SflYvx2Ou1A+jrsN7e9D+xrqT2GcXShncYnzjfFqSf8x3nEex9cfkv43yfVvk3i2ZB3isSXXt+nEYRvat856Tsl61k7+t6R+S/KgdvItu49qkt81qRfrT538Hjv3fW+fyu7La/e53j50brzmel+Yu5+ljFsWOt+1PZcAAADcKp/rrDMAAAAAAAAAAAAAAAAAAMCa+b01sLT9xr50Og7ignww/7niVoVOHoP7BQDA8wzyaOHxKOIsP80HeWdeLG4d/H8gf7EeRV4A/7Q/2R/ss4B9BQAAAID5+B4IAAAAAGCdfP/LovPzGwguB8s= +1438613653.950,1.000,0.013,HISTggAAASp42u3dS27CMBAAUH9C2iL1Bj1Ab8HZKvUGXJQbtAtgM9XISVUqUN7bWLGTeIwndlgg3j6PH6Uc38tZv5T1XBy+DqcCAGxTFfdN+6n/HFddeV1P2ls4jtdl7S0p43XTwuOncHyNd5fE0ZLze6jfhTI773rfOZwf44rx9uT+Me45lC3cpyZx9aT9JemvJuPeX8rXJB9if3My/n0S53Noj/M1JXkyDeY11veVeVmT8bYkH7K87sl8tKS+JOMfPX9l0F8dPHej9aEvfL6zcSztf239b9e7duN19F73z/pH48J7l88N8w9YZ0BeIQ8AAADfWwAA+y4AAAAAAAAAAAAAAACwDX7/BAAAAAA/3dv/DwKwrf1iK/EhfzAfPHZeyUusewAAG35f+wZiKAaW +1438613654.950,1.000,1.211,HISTggAAAWp42u3dwW0CMRAF0J2lDSRKoRkaQaIDGuXIDSTgioBdFjOe9w7JBRL7e2xPFBDrw3E/xO483K0e3+P2dXO5bE8DAAA8E3/ye8YXj4sPxxsT5znOnN/U8a1ePH9qPuNC6zm+me/c9Zyax1Lz/Pa+iZk/N748r1+fC0OScZinvAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyMvn2gIAAAAAAAAAAFV4vRQAAAAAAAAAvC/MGwAA0E8DACza/+ifAPx9CwAA1ftt/TcAALTvy4Hf7Tv7D/cJqGcAgCX7FX0N+nVQ36C+QV0D4P6QN/KWN/LOlrO8Aec3ALg35Q2A8xt5y1s+8qZt3qF+1HfDOomkeUfROml1jkTScVe9n7PmnfV9nL3f71nzdr7U3pfy1nf3eH73fr+Dvr7e+KJI3v9yfkdn9Vtl/1sH+cgd93yOcVf/P1/rPsP745zTLXMJ69lHfVwBAeYG9A== +1438613655.950,1.000,1.316,HISTggAAA8542u3dXW7UMBSG4RynpUhsAYlbdsF22AYSO2Cj7KBI0N4YWZl2Etsned6LRjO1nOPz89lOMjOff/76scT3r8s/1pdj/P375fn52+8FAAAgM3FR++Od/cSN/4+N9uXl+FC9XqvXrf7qdh9ubL823v9QHR+r12v1fuv/r+N5aoyrdZ66v6Xq77Hq77Eax0N1rMf7VL3+2GjXOl9p+O+h4Yc6Lq/tPjXOV8e7bNj52LBzyx+teKwN/64befnQyOf6/aXhz61jq07LRp3U511vbF/XxbIRn1b7pWHHlk5s2b1u+PVenWzZsaVncWP89tb3uLP9bPNp3Jgve/U/y/wfk/d3VP/Z11/WqwDoDAAA5jNAfgPXrr9Qx/IAAAAAAAAAAAAAwK64LwcAAAAA9mkAAPMHOwEAAGC9CAB0iy4D6hiAOqUr/LHHeEM+AADoKADAvABgkrqOk49PPvCHuED8AXUBAKDr/MU/gLzlN0CdQF4A8hpQf+A/fuJ/QN4D8g9QVwBA5wCAjgIAAPMwoC4AHFX/9AMwz9MD4P86UA90EvKIvYB6B6Du6Ag/Yly85QeuqF/y3jwEeQDIW/59q10hHu9qr46uWZeR1G6YT66s19l1gB6oV5w3/iHf5QP4374K8kfe0EPjpNuQZ8Yz0H7PYV1jvFfVcfE7t//MP+JI966r25E0L6+qjzHIvuznoWM56yzkNd1LPI44qV+zxs9651iKlKXbxsPfO/cvr4E8+Tf7OqCcPF70pa9f4mTju/d6TFzEf9mez4vBeXHveSNJXO/tJ/s+Wl4fq5f32hGTxquXv9dO9sdF63Fvfy9J/B2TxyHL/JfNH2VwHEbp9971N3q8sz0nGJPbN8s+a3R/e/fTe78zOg9GX7e42n3Lo3U6OsU7i/6P6veq142zPPeT/TrJrPutOFn+9F5Px0663XseiEnqorcen+W6w6g4Z5knZ/++zlHzXq99xDK4fmMy3Yid91Oj94ExuH6W5HbHQXrRW/e3zrO+0Z5ycLxv7a8k2+f22m/EwXbMsv4qg/KPbvddv8xeD0fZe5XPj5aD7R61Hj3a3+Wd5y2D7Z71cxOxs7+zfR531nnnrXUbk+dhbKxnZ5kHt/I/S35H5/ocXY+j8qPlb5/fOMbfLT+XwX6PO+szy7q3TOrP0fuyo67Xls56u5cuz/I8zlvvi2bNkyz3u49a9/VaD2f9HqDZr0vN/vuNvgdobn9nff6zJPH/rHqSTQePWtfGSet69H6n1/yUze6zf9/jbLoTSe3uNZ5seZf1fucoP6f9/cg/+1UJKw== +1438613656.950,1.000,0.025,HISTggAAAWB42u3czW3CMBQA4NiOKXSG7tLRqkps0AU6QkfrBu2B5vKkJwMKBcT3XSw5dpz34p+IAy/7j/dp+vqcDtpfWQ7F28/r9wQA3Idy5f6PkrdR+zpoX47sN2o/qi8r1bdBvHN4/pLUx/bx/jXpt5SbcH3p15P2PbRbym24T0+u11C/jP8Uyp6066FdjKMlcWxD/XPSvyf9dyHfmyQ/c5K/TZKnmP9dGH9O4srmSYyjJ+PUJI9zkveWzOuSzNM4P1syT/sgby1Zzy15rmy87H4lea6SvN9j4x/lpSRxZeu3Hbmf1BP3t3rmPn3qfrz2OVhWqp8G7+HWz/FH/b7gvuZDMZ8BAAAAAAAAAAAAAAAAAAAAAO6K/4MBAOen9wCAc8D5BACA7zsA+yLePwDOJbiMar2uun7Ljcf3X/crV8pTuVA/+7pzUbziB/MbsA+A9SXvAAAAAMA5/A4HAKTfCb866Ae4 +1438613657.950,1.000,0.022,HISTggAAAUF42u3aW07CQBQA0Hm0FVyDe3Fb/hkSd+Ay3Jw70A/g58abFohS5Jyfm5ahc+dZJuHp7X1XysdL2euHWPfh9ev5swAA11Hlt4p21CvXe2p+x+t24fPbwnzn6hlCuRbux3JZ/j3cj+WOn48hPoTyY6hvSPLqC+OQXI/J/U3y+TFOh7gN+W+TdkxJezbhuY9JvlmeUxKHmX4cQt7xekrGM9bbkn7JxmFMvtdn5lf/+RyUzr+ezJs6E1uyjrK8y8J8e/LctrCenoxHm2lfmcn31H2i/vJ+eu7+Ws7cf8uF++ra39/1xvKFNawz8xjnV/hf87NaRwAAAM6LAAAAAAAAAAAAAABwY/yPHgAAAJzTwTwFAPwuAcB+D+Y5WDcA3Mu+X1fevnqn44L1+xf1Vf2JftU/AOD9BgAAAAAAsFr1Gww4B2I= +1438613658.950,1.000,0.026,HISTggAAAVB42u3ay03EMBAA0PiT7CJaoBdKQ0h0QAP0QGN0AIddOIw08q4S0Aa9d7FiPLEz8ScCHl5en6fp/W06aeeynIqnz8ePCQBgjXJl+ypfq9rVC+PKRuNtSf899BO+M3/alaSsSbuW1Mf2h2R839dLuJ6T8fekvznE91B/CP0sof54Lu+S+DmJu0/qe6ifk7iYvyWJb2Gcx3C/bJx9UF/Dz0sS15LnqYP5MSXzb07mS7Y+2uA63jd7vpaUZbBus/zUwXW2nloSd+m+MVqvZZDfOsjjqBztY9fGjfbHrc6hrfZZ/XoeefGewLrDfEDeAQAAAAAAAACAPfL/ToB9AgAAAAAAAPbD3+8AcB7hvQH2JQBwLsoTYL0CsKf9vvzy/W/1XHMeytst502e9/U+i/eI9eBcgT+cz9YN5gsAAAAAAKzj9+cAgO8PAODff398AYWnB60= +1438613659.950,1.000,0.020,HISTggAAAS542u3cQU7DMBAAQG+ckCC+wEO48TRUiR/wB97HD+BQuKxYWkdILWjmYjlxbGedrtMeev/8cmjt9aEd9c8yjsXT++NbAwAuI65snBCvP3m/02B/Mdiup3F6Kud0/ibVc3/T4HhLGmdN7ZdU5nlV85+L+eb72Ir2PY3biuNb0e9Xu9ui/ZLaV/VelGtRX4px1yJu1fXV/VTlXRGfat3mYl3y+HneUfQTRT1fvxXPYyuep+nM4y3NP058PnI82vff58rP2XxmXukn4h2D+WNvXtvb/zSYN2Nn3vyt8zF4vX1XHAAA+zQAAAAAAAAAAAAAAAAAAAAAAFwL//sDyD8AAAAAP/M7BgD2Ny4VN+t0HfG0DiBfyBvYb7znAMivAGCfBADwvgMA8E/emz4AIKEHMw== +1438613660.950,1.000,0.029,HISTggAAAVV42u3Zy03EMBAAUI/zWwlKoBdKQ0h0QDG0Qwl0AIeFy0hDDCsQ7L53sSJ/kozHdlZ78/B439rTczua3ss4Fnevty8NAPgbQgjE5ZP374P1cWL8qvH6Tpn7V+Wc2i/FfZZUrqn/ksaZUv81lR/1h3S/fJ8ttc/1V+l6TuNuxThranedrufUfivqo7h/no9D8Vwt9ctxyvHuqf1cxGcq5mEqyrVoNxd5sJePU5EfUYxTPWcVn6XoF0WcRtdLVV+NW73/UrRrO+uzfXMdt6J/DO5be/tbH3z+0f2tD9bHF/fJdmK/Sz1X45fGizOJFwA4vwAAwHc8AAAAfhcCAOB7EQAAAAAAAPhZ/pcD6w8AnEsA9mXMO4B9EXmCfAB5CtaRdQ7g3ECeyL9zfr8wf8CFrfcwf4infAEA56h5EwIAwPcJgH0LAAB8JwPYJ8G6gf+/Tt4AFqwIAA== +1438613661.950,1.000,0.020,HISTggAAAUN42u3ZS07DMBAAUH+SNL0Dd+FoCIkb9A6sORo3gEVhM9LIXqAWyHsbK4ljj1173KoPL5fnUl7fylX/Kuu1ePp4fC8AALdUb1S/Hny+RuXo/SXcb4NyTa5ruB/jWJN+eyhbuN5CWZL7PfTz3f4peS/2t4X6e2gvtr8mcZxC/SWUcV728HxN3juHfs6hnz08b5PxZOPfkvH2pL2e9N+TcW7hek/iivPWk3o9WV91sD5b0k5L9sWS7K+exFkH9dsgztl2yqD9PthvWR6qg3p1cp5n89Ts8zIZ973Pj1H7LZnfMvnebz//furz4j7fMwAA8L0KQN4FAAAAAAAA4Fb8PwwAAAAAAAAAAAD8V9W4xAXIBwDyKQA4xwBw/vyx8805CsfJT0fNK/Kc89W6wnqwbs0b1gPYfwAAAIDf3QB3z7ef4KgHuA== +1438613662.950,1.000,0.019,HISTggAAAUF42u3c203DMBQAUD+auuzALoyGkNiAGdiFcdgAPgo/V7pyqAqk7Tk/luPUdm78yk/vn1+eSnl9K0f9K63H5PHj4b0AAFyzGtKS5H+7/b+qJ97XkvIeymuSn9VbJ2lsf5fUtwvpCP1ckn5/Xz+E8rtQvoTftdBeD/kl1DOS8ti/WN8+5EdID8n1nvR7JHGKv4vxyOI2Qn4/iUvMt5Xx6ZP62iRfk/GblY/kPS8r338P4zNrr4S4Zf2sk/uyeZO11yf96ivncZZvk/m+dh3qP2z/XOvfqetu29g+de595b+fb6vng3LiON3qcxXvGQAA50wAAAAAAAAAAAAAAAAAAAAAbpT/qwCsgwCA8wDiAeaVeACA/VD84LLng3kB2GcxboBrnNf1Qvt9q+tt9Z0C1gcAAMB3AGBdAAC2d074BKZtB7k= +1438613663.950,1.000,0.013,HISTggAAAUF42u3aW07CQBQG4HY63IwPLsC9sDYTd6ALdQf6ALz8yUmVgAXyfS8n7TQzp3MFwuv7x9swfL4MB9Mxjoew/95/DQAAj2CcuV4qj6Xf89x+yed63M/y0+fM9TG2iKfnV3F/iriK9rK8F3EqrrPdU36bmfJW5PN8jNsoX0V9vahnF+VP0e42rnvk2+O5XZRnPb1ot8f4ZX67qG+K99lEO63IoxXj3Ypxqvot59F6Zh71YnyreZR5DkW726Lf1sX6yNiKdTb3fLWeWrF+e3G/6u+q/emP+8d05r40/LJf2pn1XmvfnBvPa58jS51zt36OjjeWDwAAgO8nAAAAAHA5ficDAAAAAAAAAACAy/P/PAB4/HPaeQ8AAAAAAPyX8c7qBfMSuMX1b38xL8wjzPvH7A/9hfUBgH0U4wl3sk5/AAYWBso= +1438613664.950,1.000,0.017,HISTggAAAS942u3cwU3DMBQGYMdJGhArsAujIaRuwARsxChsAIeSyxO/kopLG33fxXIax8/Ji+1c+nx+f2vt47NdjL/lcClev1++GgBwLMNBx3Nv4+p38lyGsk+s8fdy3lqfS30Kx1P7sbQ77eyntlvrSzme4ppLf0u4DzWuU7hO7XctH0I/j6WfGv8S2tdx1Phqvk2lvzrusfxe46zx9RLXU6mP4X6lPJhDOYXnuITxTSEfUpnys4e86SFPUj4Opd5CvYf5bdp4H8dw3fSetb+/A+O82jfi3Dsv953nb8Ux/HNeG65cJ/euN9fG1W98/b719QkA36kAYB0DAAAAAAAAAAAAAAAAAAAAAACA4/J/X+A9BnkKANgvAJiXALDeuD+eHwAAAL4jAfMPAADs3mf+AFfZB94= +1438613665.950,1.000,0.014,HISTggAAATZ42u3bS07DMBQFUDtNUgoTFsBeujYkdsAAlskOYNB28uAqrcoghXMmUezYz784SaU+vbw+t/b23g42x2M/HPaf+48GAFyna9dN9Xv45fHqK5/fIVzfF8ZjDOXHUC61ZxPKbUv+VPKHkp/Sa/4upJ/O5xB3W9p5Sr8r/Xs4Hu/L9bWfY4k3lvpq/B7KTaGexzCeu1JuCv2q49FCfUNoxxzqb2E+xzBeKV7/+fvl27qr9cxhvc1hHY0hTp2PpfO0PttC+hjiDmHepoV+9HJdWxi3FuKn9H7m/rPUn6V9uYf8S/fvfuX5pfvw8EffB/rK2gMAwG3/LgMAAAAAAAAAAAAAAAAAa+T/ceB+BwAAvK8DAAC+EwAA8F4HuJ/BfWz/ALC/81/HtesX5gkAPEcBYC3Pty+f5AdK +1438613666.950,1.000,0.018,HISTggAAASt42u3cS07DMBAA0IyT9IPEDbgLR0OVuAE34IQs2cGiZcGIkdOmQgW9t7GcTMZOYsde5eH55TAMr+/D0Xgq41g8fTy+DQAAl4h/1p+4Ut5YeTyKstI6eXr9jJRnLPJORXwU11X18ed9afd4leerX3MRv01x2/QcxuL6zancp3or2t8UcXNqd5fq9+l4LudU36f8UzofKf9dyjMV/ch5W3E/u+L+ctkWvv8cV72XtjB+LMbt0BknVb525vjsjedq3kbRj9bJHwvzRXF/sfJ71Gt/uLCddub39rfXo1h5ful1ceX4W13Pb20/AQAAAAAAAAAAAAAAAAAAAAAAAAD8Hf5rCPgeAQD2DfYj9oWeJ2C+AIB1HwCsF94fGJeA+QzmFwAAAADAd/EJIlwH6Q== +1438613667.950,1.000,0.017,HISTggAAAUh42u3Z3U3DMBQG0Dp/DWUFdmE0hMQGTMCGbAAPpTxc8altCFDRc14sx+6tc+04aXr39Py42by8bvb6j7Lti4e3+0MLAMAyzXgWja/9UT66I8e7EKd+rgvlEOKP4fyH0D4ciduXMo1jKvVtqc+l367UpzK+rhwvz9ef8acwrpsQdyrtY6jvyvcc6rchnzUPc8hvjd+H/nNp35b6EI7XPPQnrottWH9DmIc59B/DPPZH4g1hvaX1l66PMazPtC+0cD2m63cIn+9Lvz7EP7We9o3f2n+7E/e5tnB//O6+3c4c/9L7QfvhvKx937n0+zLg9wkAAAAAfMV7JgAAAIDL5v0NAAAAAAAA/4H/vQAAAADW5X2LeQHAvi+P8gNgH3QemE8AAM9l68VpFzYu5Feer299tCsZh/c9ztf6xPwBAAAAAMD62jvh/QgX +1438613668.950,1.000,0.014,HISTggAAATF42u3a203DMBQAUDvOgzADu3Qd1kBiA8SebAAfbYV0xVWiVEFpdc6PFdeP60ectsrL+8dbKZ+v5axd0npOTt+nrwIAwK+aXNfk8736rxvjLX9/7yvdQjstlIv1hqS/buU4Yv0+yW8h7ZPrLP8az3xJx6TeGOpPSXtziDO2U5N4utDPtdxTaK8l+XEcLbQ3hvJTEucQ1mVK+u2TenMyT30yv3XjutbQ/pjE05J6JYznOeSPC/1n7Wb7dEz2XUvGXZM42sL+Xnu/ZvMx3Hi+ZOfCkm5juW7l+ZaNZ6l+3fk8fvTnYblxPvdu5177BwAAAAAAAAAAAIAj874tAAAAAAAAAAAAAMCxee/buoJ9Z/4BAAAA/oP/L8wLuB8fO85qve963J5HAAB+D8Hh9+kPnagHHg== +1438613669.950,1.000,0.011,HISTggAAAS542u3aS07DMBAAUDt2wu8K3KVnQ2LFlouyZAeLtptBozRqhJr0vY0V1/E/E7fq6/vnWykf3+WondJ6TA4/h68CANy2utF+Dzcyb3Vn612vHP+wsJ1wfvyzrvG6Jff1UH4M1z2Un0K5FvKnUH4M+S3Jj/Vl/R7CfX2mvjEZ5/n6IanvManvKSk/N75Y7xjaP3/+ckqfk/XI+tuSNBt/C/W1mfrqhe1OF+6znpTLno+a3N+TfR/3yzDT75600xb2uyXjHWae+57s/zLT3tK4WhfGrbXaXeu9WVeOx2vF77Iwf6vnh72dhwAA5wXzDAAAAAAAAAAAAADAHvg/OQAAANwfvwcA4gQA3lNgvwMAzhEA/H/8FJ8BAMD3jL21B4hX9zLeal7Bft76+v0CETsHog== +1438613670.950,1.000,0.011,HISTggAAARJ42u3WS07DMBAAUDuJk3AH7tKzIbFhzWU4FjdoF2U10jARUlsQ722sNtOJP2PXz6/vL629fbSr+avt1+Z0Pn02AIDv9D/+nl58P91pnP1G/e9FfBxnC5+nIt+U5JnC/bLK10P8XKxDzL8l71uK+Bi3hLg9xI/wfBTjfkry7yF+C/Fr0t8Rnu9Jv2PcSPoR47bQrklczBPncw3rtRxcl5GsY1yPbFyxntZinbN5G0k9jqQ+56Qf88F9shR5q30+F/uyF/sw9uPo+fLT83Iqxnvrc+9e57D7gfvGI+pDPQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/11/8O/NKwDgfxjAeQb2L2D/mEfUD7+sDi7yrAdR +1438613671.950,1.000,0.023,HISTggAAAVl42u3ZwU3DMBQAUDtxWgoXBmAXRquQ2IA1GI4N4FC4fPRlGhqUkvcullv/2I5/nKR9eH55KuX1vpyMn2U9Fcf3x7cCAMDy6sx29cL9ZvWsn5a0H0JZwvNmC/UvU/J9C8fP6rF9C8fdhfoYPj+EuCGJ34f4KZQx7hDib0J8LPch/jbU78LxWjKOFvqL8xySecX1jOMYk/PSOuuRzTdrPyV5uOvk55CMJ+ZnTfodOu17/Yyd9lk5JvPO4munvzG5XrN59fornbh65v7WixvObP/T/e7S++ilj7N0P381zjIzH9Y+/jIzn5fqp25k3f9bvv82bq3XAde9PlWe4fnlaveHKn/MBwD7sfMBrjOu4rzLAwAAAAAAgG3wvxAAAAAAAHzn93MAAAC8B3tvBgAAvBcA4L6AvJP/gP0CWMv1bR8B9++tnzfrAQAAAAD0+B0RAADY3HvQB7t8BxU= +1438613672.950,1.000,0.016,HISTggAAATZ42u3bwU3DMBQAUDtOmyAYgQHYgtEqJDZgAUZkAziUXr705TTyoYreu3zFie0fO3baSn39/Poo5futXLX/WK/h8vv+UwAA9qh3lh/l/h6l3ZrE6c5+pqR+LG+d6+L5OZTH9p6TPGP9c6e9Uzgf821JezUc3+Ia4pJcF+MS8rkdP3XG5Ryua0k+cbxfQr0lxDX0t3bymDvjegpxCufnjc9Pdr9z53koybxOSf1zMh8tGY+2sb/Ybwn9tGQcYp4l5DUl7cb8snVbk/yzdktn36gb623d1/buT3Vnf6P21VH799b86+C86878Rpc/6vv4KPkA+B4KHHVdWe8AAAAAAAAAAAAAAAAAjOR/awAAAADAaH53BADwOQwA7wUA8F4Fz537N49gnWB+AfsdAAAAAMCx1D+94Qco +1438613673.950,1.000,0.012,HISTggAAASx42u3bQW7DIBAAQMDG8RNy6F/ytkq99pSP9gftIUkPK61wWlVJ5ZkLgsBig7NGkfLydn4t5f1YLqZrWS/F6fP0UQCA/61agl3sb/3h89CSetsYNxufzXM7b85JeziPfvebk36xPYvXBuPiPD2p9zDuEPqtoX0N5e06lqSM8y0hXg/xsvs4JPVsfadk/3uyHktor4P9acnzGu9zHezLlMSbk3pPxvfBfZfk+kfPV0vmqxvXK/u8bty3GKclccrg+5nljXkQtw3Gt43XUQZ5Kctno/w26l/uzH/3ahvz8m/f88/+3t/7ucS5zHoCPDLvyZsA8iMAAAAAAAAAAAAAAADslf8RAeD9AyB/AQAAAADAI/jdHXxfAAD+6vxTnaucXwEAnJd4lv3+AggXBrE= +1438613674.950,1.000,0.017,HISTggAAATh42u3cS07DMBAAULvOBySOwF04GkLqgj0n4IbcABZtNyON7IoIaHhvYzXN2ONfYnXRx+PbSynvr+Wknct6Kp4/nz4KAPA3Vfnuerx+Ku92ZR5ZGR065TQY18Ln7L45uV6T+6bB+HA+Tj+XTnsltHspl1C2Tn4PSfwc4tdzeZ/kvYR61lBe6rtL4tcQPyf9i/XGfNdQ/5yMQ/Z9Ddez/mXrKZvvKbl/SdZvnL+WjEdN9sHUyTfut6y+bF0unfsOSf51MG70+ZXty9JpL6u3t/9Kp/3R52ztPA9Lp73RuNF6y0b5lyvHY6v30Xf7d6vvc3733GTdAMD+f9/AuAMAAAAAAAAAAAAAAAAAAAAAAAAAAP+D/10FAJw3AADnEW5xPqr+2n/2D+Zzl+vDOgcAAAAAgO3ULys7B3E= +1438613675.950,1.000,0.018,HISTggAAAS142u3aS07DMBAA0EziJAWuwF04GkJiwZ4bcEJuAIu2m5FGTkspAt7bWI78GcefpJ/759enYXh7GfamQxr75PHj4X0AALiGMP6LGDv53F9sjGcsrkeRjkX/Lb135nRO+Zaut065lvrdFfWrOI7ll0N6k+JfU/tVXHMq34p6czH+luofr9+muNaULp325mLcuf5YtL+mendFe1X9fN+XlOZ1tRbt7jrrrRX3I9J9qNb3VORz+1Gsq6WzL6KYp7Eol/fd1CnX24+98cbG9oeN+aGIf2vcp56j58YXXzyX48R6lzr3f9vzM/5YP3jvBMC5j/kEAAAAAACuw+8EAAAAAAAAAAAAAN/L/zXBfgMAAAAAfH/3X+fJvAMAAPicBGGfAQAAAAAAAD8sPgFH9AeK +1438613676.950,1.000,0.010,HISTggAAASN42u3cUU6DQBAAUGYBadQD+OFderYmJh7Ai3oD/Sj9Gd0uBENM+97PpDDMDlu60H709e3j1HXvL91ZP8c4h+PX8bMDAK6LjfvvfX7W1smx7NzHXvPU6resPM/S2B6N/FLJj8r+Sxwaxw1pnMvrcY4Pc5zS/sv2Q4pjys/Hl0qdnDelun3KGyt9DJW8p8p4jynvkLY/p/GHRv6U8vtK/yXV6Sv9l8r7leuOlbpjZb7i9+8fP+oMjXGXXqelcb13jeOiMU5Z+XnqK5/bvtHf0nWhLFxfYmG/sXBelq4/a+vFxvVy7/vVrd+fY2Ne/PPzAwD3EwAAAAAAAAAAAAAAAAAAAAAAgL/nf50AADy/AQAAAFzj9wgAAG7pOTLutJ/4BsKlBqw= +1438613677.950,1.000,0.011,HISTggAAARp42u3XPU7DQBAGUK/trE1OQMFdcjYkSjoumhuEIkkz0idDEMIS7zUrr53Z8Yx/4pe3j9dheH8erqbb2K7D6XI6DwAAj2jy2tX5tB/WoYVxDvNb64/l/+dU5lvZvo+HsN60MT+VfOeQx6Hs72W+h3h1nTWs18P6S5lfwnzKL433459u4zHkMYc6LCHPtWwfy3aqe82v9rX2o4d4qZ+pTlOpQ43fwvFriFf7V/vWy/We6tvDfKrHHK73dF+1jftqCL+vcdL+cSPe1vOnffH5lM4zHTf+8nP10bz/6v3TvtkX710AoDkvAAAAAAAAAAAAAAAAAAAAAACAf63tPB7qDAAA+P4BAAAAfLerL+qtHoD7Xt3YTd8/AXBSBto= +1438613678.950,1.000,0.018,HISTggAAATt42u3aS07DMBAAUP+Slq44AHfhaAiJG3AELsgNYFG6GRilTahE0XsbK/X4G9tp1D68vD6X8nZfjvpXWo/J08fjewEA+Avqxrj6z8b5W+Wz+JbkZ/E9lBvhui+035L2T+kU6plD+X3I76HcHD4/9W8XrmP8HD5vSf0jGf8uiYvj2Sfxh5DfQtwh6e8cyp3quwvpCPM4hfp7Mn9zMt8j5I8z53NO1l1N5ivOf0360X5+z/l2P0ZSfiTrsyfrvif9H6H8tLA/pqR8S9ZZT/ZPWZjXsnDdk/yWtFPPPG/qxvPt0vOprIy/9vNjbft15XiXzvdbe97hPgDb9rf9j+cVgPMPAAAAAAAAAAAAAMD/lgEAAAAAAIBr8XskAADgfcH9c/8B8L0FsL/BegOcU4B9CgAAAAAA3J76CZutBw4= +1438613679.950,1.000,0.015,HISTggAAASl42u3bbUrDMBgA4CRtunoH77IreCXBG+yfp/QGCk5BXnhNRx06eZ4/IU3SvF0+Ggq7fzo9lvL8UM6mj7Sek+Pr8aUAAHw5H3Cj49I2th/l24VxzeGcmd2nhnqf6SHcJ9ZroXxOymsS1xLyPaRLSNek/hr6vUvareF6T+r1JP6s3Rz6jfHFuFry+y4b89NgPGI+3qeF+LLrPRnfOM7zYF5MSdx1MI8Og3kcf+eaxNUG8fakvCbzoCVptn6z+iXpL9s/snVbB+stS8vG8rZzX6xX2lcvbd929lev9Nz1h5//r59L6i/3DwDc3rnCex8AAAAAAAAAAAAAAACAPfxPDfMTwP4IAAAAwPd8jwEAAAAA4D/y/Rusc3GDfQAAvI8AAMC5+739G/gLBv0= +1438613680.950,1.000,0.014,HISTggAAASt42u3bS07DMBAAUI/dpBVcgbv0OlwDqTdA4pzcABZNN4aR088G8t7Gav3JeBI7iaq+nN7fSvl4LWdtKeNcHL+OnwUA2Kb4Y8eJB/WPf3reYuXnNshLTcavyedIxr/U75dy6trtknFaV79L6qeuPuvXunLu6vdJv9qVWft5cPxLnM9Leej6zcl4fd7mZP6ta5/Fd0jiHOX/aRBXSc5Hn68sj1MX3zSIL5J5Z+3L7+9BP75vg+s0u55ikL86WB9tsE5rUmbtYhBXtq7ryn1ytE/ElftRuXI+a+8Ht+7z9cpxR8dpd8YdN84rHhT/veN7bkLeAAAAAAAAAAAAAAAAAMj4/xkAAAAAAABb5HcyeQMAwHMeAIDnKwBwvwSsbwDcVwAAgI29Z3wDy9QHFw== +1438613681.950,1.000,0.009,HISTggAAASZ42u3aS07DMBAA0IyTNJQzcJeeDcSCPRflBiCRZjPSkNKqCRLvbSwntT3xt1H79Pr+3HUvb92sP6cxJ6fP00cHAPxP8Ufri43ivbbe2Gl8YuV6uzC+fL9P5buUbxe2t+THIr+0M6Xrh3M6pPt9EV+uP9cbqb6l/oeUH4vnOhTx5bRP+SnF9ZjuR7o+pvwxff6Y6s39FkU/Vf1dxTkU/ZvjacV45faHYr72RblI7Vblq/k9FePdivkTv5zXa+VbMS5dMc9asf6qdVelsbLe19Zvta/Ejfvg2nitlYsL49r6PLi2n+51Ht37/I6N2t/reff+vgQAzjHMJwAAAAAAAAAAAICf+d8igH0dzGMAAAAAAICZ3ykAAADwvgoAAAB8v49/AUcvBxI= +1438613682.950,1.000,0.008,HISTggAAASF42u3cS07DMBAAUNv5AldAgptwMyQkbsBFuQEs2rIYMXI/aiPEe5tRUnsysRvL2eTx/eOtlNfnsjPsY92Fp6+XzwIA/A/VEFw0LvVK1+mdr5329ch87cj8Lewby+/7yJ92LZwfQ/uYb0j6xbxj0m/axyXEw/k1HB/yzkm+NcQ5qeMhqWsOdRyO70LeWMecjMcS+t0n9xt/n8J8jeE6a7herC+bpzHErF3t1BHHYejM9xRiS+pqSZ1L8r9vSZw6dWXtpuS+sudr6tTdknxZv9J5nk597obOutKLw43W4XZm/nphu1auq545LlvtE+rG41A3um/7NADvwwAAAAAAAAAAAAAAAAAAAAAAAAAAt+B7igAAAHhPNG8A/JH1/hsVVAaz +1438613683.950,1.000,0.016,HISTggAAASF42u3cSU7DMBQA0NjOoPYM3KVXA4kbcIBesTeARSmLLz4OalEH3ttYaTzF30OURZ9e316GYf88HLXPtByT3fvuMAAA3ILyR+Xrg4xHWfl7TfLVzjjFetrKfGPSfmx37MSjJvnH0J8sjfnm5P4pnZL2TuWW79+fv663Id8c6p1DvUvSfus897IybaHeTbiekjhsO3GqyfPEcd104rUk7dUkXnE+ZXEuSb+zfsR2p878i+ulJeVKEo+axDtbt2Nyv7f+x864tc46bxfaD3r7zdp9fm358st9vlwo37nn1bXOyXvpX7nT+AAAAADALfPdDAAAAAAAAAAAAAAAAAAAAACA/8z/8AAAAADwE9+PAHBu8ehxN28AAAAAAIBrKx94MQco +1438613684.950,1.000,0.010,HISTggAAARh42u3a3U3DMBAAYP8FgliBXTobEhIDsATjsQE8lPBw0slJRRFSv+/F6vVinx3ZUas8vbw9l/L6Xs76d1vPzenz9FEAgNtUzfcm17NO6q2T/Lbzc5uM15J2hPwe4iMZryfxh3DdFr8L8fvQz5a3hHgP8dh/T+a19b+GtoZ61iQ/jrPlPybrE+tckvnEcVuyHiNcP0JdLfl+SfppSd4a6urJvEZy/+O8s/uwJHX0pG3JuDXJqzv3RYyXpN8y2Xdlsr/qzn15NJ6Ne7Sfo/O/tL4yWc9rPQfqhdf99vl+9Ly/Vn1/9fxsBQDw/wAAAAAAAAAAAAAA8N95nxkAAAAAAAAAAACAI7x/CuD8BQAA8LsFgJ/z/wuorgcv +1438613685.950,1.000,0.441,HISTggAAAX942u3X3U3DMBAA4JyTNLADa/DMbEhswFKMwwZFgvblpJNrVEF/vu+hltMkti/na/P09v46xfPH9GM+tPH9ue33L58TAMAtiUPbBs+v+n897974bfB4FHFpne+X1J9T2zr9uRivOu84/u7Qrmkex/7WuV9L5+XrHtLxXTq+dMZf03j5+mrdS9Hm8473eSzGy+2S+nm91Xl5/nNnfWsx3xzH/BzbiflS5cV6Yr5W+ZTjv53pfr16sRTri06eVHUsiv3eOvWjde4zdfZpDNan6NSfOPG6afC6GKyzv+1f6+9hXOi8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAID/E9YJAAAAeL+/yHEBAADwfgsA4H8IAADgPQbxB9QNxB9QLwBQzz0HYGxfxpXPH+QH2J/ID5Dv5ol8kdfce97Ia+w/uK78kdcA6if3k6fyFvD7J86Mx9vzkN/ym3PltXirMyDPxZk6ruItf2/6f+AXSzwH1A== +1438613686.950,1.000,0.466,HISTggAAAYR42u3aXUrDQBAA4Ex+GiN4Aw/im2cTvIEX9QYVtEUYHDYtWGP7fQ9dkmy2u5PNbEL7+Pr20sXTe/dlOJTx+Tnv98/HIwDArYmN9KPfeD/jQufHyvpxZv0q7tHY7lee12ov0vPo7lCORTvTj8+v38eHVG9M5Vwc36XjS2r3PtWbUnms91C0l/uR+xnFuKeivFs5rqGIW96/pPOWIj5j0Z+q3Xz9dsX+oYjvWFzvOe3vG2WuNzbmWTVPx6Ldaj7n74tiuzWvu3S8lSf7xv0+FP09NQ/3jXwyXDjf/ta6EH98/ta+L24sDlt5LgIAAAAAAAAAALh2/q8FAAAAAAAAAACcy/+PAAAAAAAAAK6L34HBfW58AID1GwAAAAAAAAC2x+/rAIDnHwAAALzHAYD1CADrHIA8BADWOwAAAO9lAAB4fgKQr8E8AwAA8B4GgLwtfmBeu06IGyAPiRuuDyBPACB/Y166X8wD40K8xds8EG/jAvMakFeuPe+FfhoH8D/zyQceGggM +1438613687.950,1.000,0.022,HISTggAAASd42u3cwU3EMBAAwKwTBxCiA3qhNIREB5RBA5RFB/C4y2ellYkiAQczH0vx2l4ntpPH6e6fX56m6fVtOpnPZZyKx4+H9wkA+FvCPP71/GNnXEvXW3F9St+TVdzWby/y2dovqX4r13N5neJ7Mf5a5LOkfu5SfU/tc//5+s0g/17kf5Xar6l9L+p7iluL/pc0zpLKuYibizx6MZ+5qM/3YSna3Q7uV863eu5VXAzWxzpYn3n9xGDfLMX4bZBvK9ZZFGUf7JdW9Fvt2yjm2Yr7uPe8qc6DKOL31h89774a3w7mdfQ8jwt9H4a88FwAwPsTAAAAAAAAAAAAAAAAAAAAgB/nfyoAAAAAAAAAAADg+/n9HtiX5gMAAAAAAAAAwCWJX9ZvfALmPQfJ +1438613688.950,1.000,0.008,HISTggAAARR42u3bS27CMBAAUNuBGNoeoYvehLMh9QZclBu0C5LNSNMkLYtGem8zwp+J7ThYkeD983Yt5fpRHoYp1ke4fF3uBQDgJ3Vj+dr6re3Ytu6xXftlnpbkjeVD8jmWH8J1xikeQ/1Svrldn+JbyBPbjck45v6nZBxzPCf1PSkfQzyFcWV5Yr/zwrxaKO/JuF9CfQ/rcQz5Dsk44zzG5D7Gzz3kz+Zfk/3aQnxN5l+S9cnWqyb94/7uSb+W7OeS3O8heZ6G5LolGd/a5zR7/pe+N9rK6649H+qT8vz13HjWubWXc23v46gb9xMAAAAAAAAAAAAAAADr+H8GAAAAAAAAAMC++L0HAADg/QYAAAD4t+/731RpBrM= +1438613689.950,1.000,0.011,HISTggAAARF42u3dO04DMRQF0HE8nwxrYBHsIGtDoqFmo9lBKEKBrvRkkEikiHMay5P4+cXxr5vnt4/XaXp/ma76V9muxelyOk8AAN/uBzz4/9OKetX+EGWLeuqD/jNej/oa5Vz0vxRxquc94s3RT9yDp6ci7hbxlyjX+N5axNvj8yXy2iPONhivLdpl/CreXozDMfLqxbhlu8wz81tinHvxfI76VuR3KMq1mK85n46DebUU87uK24p514s8q7htkEcr+qvWaRus/6rdaL32wT7w035H+9Nf7Ye/7e/W51670Xl7r/O6PVhcvxMAwP0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4J68Fwaw3wD/fn/6BLcSBuE= +1438613690.950,1.000,0.008,HISTggAAAQR42u3cQW7CMBAFUNtJKPQMvQtXayUOUKkX7Q1gQbsZ6culolIl3ttYY8hkPCFOVrycPt5ae31vV8vX2K/D8Xz8bADAY+ta8C/702/8Xg/HpTwjxCnfEuJ1Evcyv5U4zdd8I4xbiJ9C/q3UtQuf17yHMr8r477ES8j/Xc/zZL7mXUsdtZ596OsI9aa+1OPWsO563sMkX72uW+hTvW59cr1HOF+KW8jbQ796qHMJfZr9/kdYf1rvCPdxuh9aybuEevuN93v/Yf9aOL7O32vf++t9+tGej33yfAG89wJgX7V+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4Hf/jD8BdnicXfZ0HUg== +1438613691.950,1.000,0.010,HISTggAAARF42u3cO04DMRAGYHtfIeIK3CVnQ0LiADQckxtAEaAY6Zc3KNXyfY1lrR+j9XgzVZ5e3p5be31vV/N326/N5fPy0QCA/617BYc8z9pOg/Pug+c/85fQL3Xmb38q/XmwzrpznamMr3Fs5Xkdt4U4TiHO2vaw7lrWWcP4KcR5LvPrug+lP4d9TmX9LcT1OBiX3ucS4lsH+VLjbiE/ziFva38L57+E/VuIJ8WxhvxL+bmEeNJ9Svc15f0S7ukSxred4+/1vRjNH+0z+g7t/f3q4dzbIL7pxjj6H7/PR68P+p3mq39AXgMA6gwAANR1AAAAAAAAAAAAAAAAAAAAAAAAAABwVP4vFABAXQfceN++AAE2By8= +1438613692.950,1.000,0.010,HISTggAAARF42u3dQW7CMBAAQDt2HOAN/Qtvq1RxrtSP9gftAXrZauVwQK3QzGVlsng3jnGOvLx9vJZyeS9X7RbrNZy/zp8FAPhfqiV4inWrD+qr3jneO8+SjGty/UdP8mexhe+1pK+WxDV8/3iLW8gbSZ011BtJ/hbiGuIhjEfI7yGOkB+vn5J1j32MZL41mX+EeeN6HUP+IVmPNfQZ7zuub3xuPRlvSZ0teU4tyY+fL6FOSdY39nWa7L+YXyb7vCb7bkn2Z5/8DltSvyf1+yQv67sl9Wf933s+LjvPz2XnOVcfdL7+1XtgNs/i/e8+AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgKfg/wuBX+fCN8+OB0A= +1438613693.950,1.000,0.011,HISTggAAAQp42u3dO07EMBAA0Izz2fA5AgU32bMh0VBzUW4AxYZmpJFhlbAU7zWjJB5/Yytdnl7fX4bh7Xm4GLcYl3D+PH8MAAAcLw7Oq8qNvywfxfN8PW2xpfstxSnlj0Xs5UW6n/Oqeuctnra4bPEhPV9TfXPRbiva/a73sdOfNcUptTcV5YfU79zfJV3fp7xc/5rylmKceZ7uUvm5iNV65XU7Feu+dPo3d+Z5KfZBFPPSOuvcG08Uea3oRyv2RRTvV/ywfG8/VOOIK2NPb5yx87kZNz6H9zq/46D644/GvVf+rdfvv3wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAnq7+X/AXxe4GuA== +1438613694.950,1.000,0.009,HISTggAAARF42u3c0U3DMBAAUJ8TGrWswC5diRWo2IBF2QA+2vycdHKJKFDx3s8psc8+u4nbrz69vr20dnpuZ9MlxjkcP47vDQDgnsSN+/+1dcUvryvS78g19hQjxdw+D/Km1G8q4tq+u8SHoq5qvJx3SPfXuE/xkPLyOOs8S7qf2x+L/F1R734wT96/pahjLj6PXEcedx6ME8X+5/V/9Tnoxbqi2Ifd4LmpnqNqfS1dR1FXS/m53zLIm4r3uRfX0+A960Xdo/Oiyru2zl7UHRvPrSj2K67M6xvPz77xfI1vOmdj4zyxsc6f/n6MOx8fAAAAAAAAAAAAAAAAAAAAAAAAAG7J/+oBzgUAAAAAAACA/y0+AUHzBwg= +1438613695.950,1.000,0.007,HISTggAAAQl42u3cy00DMRAAUH/z6QGJUmiKBpDSAU1RDh3AIeEy0siLBFJWee8ystb22F7bm1OeLu9vpbx+lKt+i/Uanr9ePgsAwH+qD56n3sm4s7w9GcdPuSUxa9dDvZGUe4jHW5xJ/4dQbyzyHEJ/Wfkc8h1CfyO0G6HdTNr3Rd4479Ni3ucQR2if5R/J+p5CeYZ6x0X7kbzPmYwr62eG9zbD/Geyr+K6tGT+JdknNek328c1iavndXGe+qJe35ivLc5vpi3Of03WM5tfWaxr23gv1cXz9sf34G/v47px3Pd6/+/le/uo4/V7EAAAAAAAAAAAAAAAAAAAAAAAAADYO/9DCAAAALAP9RtHXQc1 +1438613696.950,1.000,0.007,HISTggAAARR42u3c0U3DMBAAUNtJigtiAyRGYSgmQGIAJBZlA/ho83PSyW1KUYXe+zm1ic8XX+s0P316/3wr5fWjHEzHWA/h+fvlqwAAbFEtwZ9qgz7Ujf2rG/sbx09JnfH4Gpfweg7j1/fvwvFT552T/EtyfBrUuzvGHuranZivh/PXeh+T+pYwTw8xXsd63j7M85CMu0/GxeuLfepJv1pST03iPoyLeXuynvHzU5L1XkL+rK8tiSXk6YNxUzLfnNQ5qq8O6i5J3qwfJam3Dr7X7cS6rnVfGO177cz96tJ9s5w579Z1qb+c79J5243+ntjadwAAALhFnlsBAAAAAAAAAAAAAAAAAAAAAPjP/M8OAABw9eeOHzmpBwE= +1438613697.950,1.000,0.008,HISTggAAAQN42u3czU3DQBAG0P0lhhroJa2BlA5oinLoIDkEX0YarQEJOLx3GTnxfp5dx3ZyyfPl7bWUl/dy1z9rvZfz9fxRAAB+UzXvP1mnnuTu2+3gceP+cVx8v4Tth6SOsN9I8k+h7uO3MM+R1Mfk+D3k7PlPoW4hrye5+/YMeX0xjxGOF3Nn0scM+TOMPyXj47qOJDe+viV9j5Abz+NM1qUm/WfnsyfzqAf7GYvPWU2un69erzFvJvMtSR9tUfviusv6qIv7REn66ot5t8X4enD92jfXt/3wfloX+eB7FgDgeQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAc4f+KAQAA+Fe/U2830Qdx +1438613698.950,1.000,0.009,HISTggAAARN42u3Xy03DQBAA0P04tqEGekk7tBEpHdBoOoBD2MvAyAkhCEXvXVbaz3hm7Xidl+PboZTDaznrn209N/v3/akAAPylqr67XKfemMeY35L1LRmP/T2JO/p3oX+0U2hH/xLizyH+iLeG8Smsn8P8JcyLde1C3nOyLstvCnFa6B9xn5M6WtL/FK67hDhxP+dk/3uS95rUGfPeGu/J/sS64ropmV+SeT2pZ02ep5rcl5bE6Um+JXlupu//d30Zbxv11o19qMnvtCfXLxfm3ZL4W/fj0vdLTeLcql35Pr7Xe/qndT36ufjfz9V65fOL70X7BwA4hwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB+T/0AxRwG7w== +1438613699.950,1.000,0.011,HISTggAAARl42u3WS07DMBAAUH/alMIVuEu5GhIrtlyUG8CimMWIkROUSki8t7GcjMeu7dp5fHl7LuX1qVz1r7Jei8vH5b0AAHA7dec8dZK/rhzHqLdJ/vLzd+R3vYc8Leknizsk5Wh3TPKNuCXkP4b3PcS1EJe1OyZxLeS7D3Hj+SlpN/I/hPqIP4d859BPT8a3JPVR3oV+4zycknlYkvFn89dCvpasQw/5sv2wJOuerfMh2WdZmY2nTvL1yf+nT+IPK5/3lf3Pzo3ZOGfPSzKO7H2dnB81meeyMX/2fut5WH55rt76Hljbrm28N8rGfbP379vrXgQAgL/Edy4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMD/Vj8BVXQG9Q== +1438613700.950,1.000,0.005,HISTggAAAQp42u3cy03EMBAAUDvOD4kSkCiFdjhuC0h0QKN0AIfdXEYaOatEQsB7l5Ej7/jv5LRP7x9vpbxeylW7xXoNz18vnwUA+N+qKfhV61RPXu+WPB9ucQzlmsQh5Bt35tnKU/L7MdTP8mb9iHFrZ07am0KM+ePzx5Bviw9hPmOeNanfkn5t9ZZOeQ1xCXmy8cbyEuKUjHNO+rkm+Vuy3kMS1876xX1Qkvayfds68zOHvFNnf5VkXGPS7t5zNiX5e+c6zmfZOS/3nv/SKffq3fs+qgfvzWy8Z9/HZ9/XZed8l+Q+P9p+PZjPd5TvNQAAAAAAAAAAAAAAAAAAAAAAAAAAgL/op/4/tn4DVDUG2g== +1438613701.950,1.000,0.007,HISTggAAAQ142u3cS07DMBAA0PiTpMAVkDgK9+oOiRv0otwAFm02I41ckFoReG8zcjO2x4mTKJs+v5/epul4mM7aJZZzePl8/ZgAAPak/LN6y43mqYPjLckvIdYQW1LXkhzf2j1pb+POSV6soyd5Mdakrh7m2/IeL3Ed1Bn7b3mHkPcQYuy/hHlbmH9Jxt/6PyX1jdY7h3n6IC/WFa93vA7xvK5hP6yD69+SfqN9Mg/aPTm/2T4f7bPsPom/99Cek3W15D7Pxs/my8apSbte+dy5to6fPo/qYP3fjb/lub2391XZ2TrLncYrf3S9AID3NAAAAAAAAAAAAAAAAAAAAAAAAAAA7In/AQUAAO72/fEFHAYGcA== +1438613702.950,1.000,0.009,HISTggAAAQd42u3dTU7DMBAGUNv5gSAWHIC79GxIvQEX5QawaNhY+uQgAlKl9zaW5fF47Dppd329vr+Vcn0pN9Pe1ltz+bx8FACA/1AdwV2c43f+FtY7Oj6H+OlgXB8/d/00voS8z138FOIf93br+ktYp8/zsLdrN7528+ewr1Tf0uVZQt4W5m9d/NrVu3T7nUL8GurawuezhP5ov09h3hri03m1sM+j55/uXX+/aqijhfE6uPclrDcNnoP0fI6e11RHG9RdBvevDtYtYb+jOusP34/tpPftaN36y/xnxdeT6vO9j99LAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcB/8j98fn+8XFewGuQ== +1438613703.950,1.000,0.006,HISTggAAAQJ42u3YS2rDMBAAUOtjxb1DoUfpnXqDQhbd96K5QbpIuxkY5NAGCn5vIyRLmpGsyJDn8+f7srx9LDftuyy34uX6elkAAPj/yoPmK5P526R/Tfr1UG9JvzXM00N7HF+T+gj1mMcpjFvDuC08H0k+Me6683kLefy0P4V4a4g7wvge8h1h3i1ZT7YvWbkm69hC/ZTEL5P5anIuYh6xvSbvPTsfYxKnhH4lideTeUaSd915btvk9xT7Z/dBubO9J+eiJu2z+2eW32zdv73/yh/dm3v3L1v3o+//e987x/7OW5f9AAAAADgy/8MAAAAAAAAAAAAAAAAAAAAAAAAAR1G+AFBTBt8= +1438613704.950,1.000,0.001,HISTggAAANt42u1VSQ7CMAz0kgYqOHHmAfyib0PiB3yUJ3AguYw0clWhgiLPZeRkajuO61wfz7vI5SYfeGNtbMtLEolEIpFIJEaGDp5X5MdAh2yEnegcuAQ6A123J9AJ2Vew2blKoOv7B+AevxI/DnaB9VPjufGR5D1BnBrErcQ+k/wczlWAWbxKvptgHXV4P3PQJ+w+ndStBvUvcM+F9J2QPtWgjhb0kQVxor5GOPHvwX+K53IyH5TkYyvnigY6I/bWOcby9p3nva5ct53i6Zf8jfqu64/roX9al83+3gxSBQ4= +1438613705.950,1.000,0.002,HISTggAAARR42u2XQY7CMAxFY4cWBq7ASWYz12I70tyAi3KEWUA2X/pKKSAKvLexEjuO6zhWuv87/pbyfShn6kXGRebPqQAAAMCyCFLwUXmOmfrefHTsV+09KPPV+EmRo9injJ3/EH2Tg6yvZv/B2IeRLc61jEdj1+RW/Kt+I37X5nsGmd/JuIi/Zv9l1ru87cy5aFyq1zxqHGnOTeMLE+9g6sPVwWjqIMUuOt/j8lY78ZTOvk6fpt6n3q9i6r1X31P3cXHVK/tUTuwvMXN9zOyTt/bfuLEfX9vn487+3uU98ejzT94pHxlnkIeXzPez8pncH/oK/W4x/7/cD/oGUB/kBwAAAAAAAPgfBQAAAADeVQAAC+nD/+VQBZQ= +1438613706.950,1.000,0.002,HISTggAAAPh42u3WQQ7CIBCFYaaAWF14Ae/iwgt5BRNv4EW9gS5aEvOSCW1jTKP/t5lAyzgUSzne7tcQzpcwiGO0IRyep0cAAAB4ZyvP9+/P22a2vfE2s57a30l/bW/GmOTcWeNWovYnade8eYxFonc9S54s9e2lvZPx2u5l/kXq13kn6Y+SJ8t9xZn/RubZO/mL0695shO952TOOur1JG2tO0qd1lh/7/9jzrpH6e+c+716ovNemFNvK39rvEn9wXmfWnnm7gfWeJ+n7gs2se6l+9zS3/fqWdt3yb487lfPFZwH8Il9AAAAAAAAAAAAAAAAAAAAAAAAoLIXQKYGpg== +1438613707.950,1.000,0.001,HISTggAAANh42u1XSQ7CMAz0QtJF4saRv/CtXpH4AR/lCRwIl5FGTlFRQfJcLDcTe+xaSXu+3a8ip0Ve8Ga1Wbs8JJFIJBKJRCLBoTvltU6ed+q3znqUxHeI40SvET4+L+AfwBbCH8BX4OH6SPK8eRPom5utwMN9I/BQ9ww8g/UjrOP+KYjjpL5K+ol9qeCz/heybwC/kjmz4D1qoMPJf4ySvkqgA+c8ymPAkyBPpIvVwfJaoN9W6vnUV3I+2I+dn7ry/Nz6HtAvx99K/1b17HU/5ndDIufm/+ZMnwgfBWw= +1438613708.950,1.000,0.001,HISTggAAAMt42u1USw4CMQjlM1NrNPEC3sWzTeINvKhHcOF085IXStTEBW9DKPCglHK9PzaRy1Pe8F3qLu02LIVCoVAoFAqFf4B+6Gdg10keJp3wDiwgG8SN8xV0B/8D+BnhbWDHOJSN6Ar8ndTXSf5G+Ef8CXhGnUdSP8pG6jqTvCvpJ/a/B/1ych/WFyV1L2R+8F1RV+KP507mjMV7MOeMV4N5kuCe7D9p8v8JiZfJ/25Jfw3ySHK/2OQe0+S+y+43Se7HX+/xb8WHfXgByCEF2Q== +1438613709.950,1.000,0.001,HISTggAAANN42u1UyQ0CMQyMj00ILwqgF2pD2g5ojFIogQe7n5FGDihaPp7PKNF45NiOr+vjXsrlWT6wjWVjvb1KIpFIJBKJROJ/kC91MqjTQR8FFqJfiN6InxDdfq4bn4L4nc+gx3jMwyDvSthB10i8Axv4YFyHvCt5vxO9w7lBXId7B3+si5Ozg08n9+z9jfTDSf+N9Bn9JZgv7A/ObSXzGvXTSb5G5kWDOPYfZfD/RP9Kg7glyMMCX4QO7i8J9smsfXjUPtZJPnKQj0yugxxU/5/zfQPrfQXP +1438613710.950,1.000,0.001,HISTggAAAMx42u2Xyw0CMQxE7Xx2A1wogF5ogZaQ6IBGKYEDy+VJowSBBIs8l1GcZDJ2PlIOl+vZbH+yB/LCvnA63iwQCAQCgUBgDfCV+PIX/av5/mZdEtjRtsH4E2XQXxXtDE7QbegvmD8jnhGnLsdTr4nxm4UnMPNoQm8r5u/QX9BPvQp/k8inwDd1Z3AS9eA5UPmmjg+1v3nwHKj94nnmv6Z26sfxqp068SrWzaKOLu6luk+8h7mjx3pZJ8/eOsrvp97hX33H/8W/f0nX76kmBTU= +1438613711.950,1.000,0.001,HISTggAAANp42u1WSQ7CMAzMOGs58QAO/IS3IfEDPsoTOFAuI42sVLmAPBfLqZfJtHFzeTzvKZ2v6YO8W+zWbq8UCAQCgUDgP4CQ4Kd0wEH+mMw3x4dT35znoHsm3zcL1cm0Xmm9k38S+d+8jfwmeHDeELwH8ajEdxCvTn2b4FGJzyBrol4VdTu9F8Vb6VQofiO+jeKK4FeIB+vOelfaL38fEHpk6tMpvglf9TGHTxffvYrPor46fxD7UnHqvEHo7/VR59s75zY5l+DYo/MSk3Nz1bzGojqr/w9YrAPeuYQFFw== +1438613712.950,1.000,0.001,HISTggAAAMh42u1VyQ3DMAzTYeco+ugA3aWzBegGXTQj5ON8iBKyCxRoAfFD2KEk2rHl+/O1idykwRtrY3vskkgkEolEIvFNaPp8Cxv0YZ06JXoDPnUTiUOdBvlLEOdEhxzpHMYLiTt5bVxBX2H9heSdyHiGuAV4hvo4f218gbysHsuDcQ7rq+Q76layP07q4f4KqaOQD89PJb6i/+5BPnbu0E8B3xacJ+30o8F9MJIP77UTPesDvX3FBvuiBj7kw/lffz/+5f0aXtcBXC0E9g== +1438613713.950,1.000,0.001,HISTggAAANB42u1WQQ4CIQwsLVtW48EH+Be/sd8x8Qd+1Cd4EC6TTIoeVg+dy6QttAMUwuX+uImcN3nDOpfOen1KIpFIJBKJROJ3ULBLYAv864SMZ4zjjOhYwI/j8V+5kHwjXokehfmts8O8BrZCveE/gu1B3Qp5GthO/CvR66DjQHQ2iEe2Q93BJ9i/Ferg+hZg1G2EZ/vBgn5g8Ury4nkryRfFy6Qu7H92b3zyvhWiI4obOT8J+pnddwvWo8F7o5Pv2awe+TDfXti73r/q+/bcygtKmQU5 +1438613714.950,1.000,0.001,HISTggAAANp42u1VQQ4CIQykLQgYEn2AD/Hm20z8gR/1CR6EyyQTMO4mHjqXpqXtdGBhL4/nPYTzNXxg3Uq3ensFh8PhcDgcjl8grnOXfkJsWPRnfRTyDOIjL3abYN2gHutG/oHwJeiTIY7zGvTLJF4Ir4Ie1HsE/kJ0DFvBb92eoK4BT4I5UE8Dv5D5MumrZN8i1FWIR8JboY8RHUq+L8Zv5HyM+LP7wM6XzRfI9xrJXLqoN03uJavTybqQOZgeW3xflNxPxi+Tc9E/+U/I5B3cqt+32Gt/ZOM8Wv8G++UFMg== +1438613715.950,1.000,0.001,HISTggAAANh42u2WQQ7CMAwEbTdpqIAXwF94GxIH7nyUJ3Cguay0chESFLRzWTWOt66bpD1ebmezw9WeDLP6rHG6mxBCCCHEP+JvxtdaX4DiuMN/H/pF4mOQ37WAj8N4kHiAzwjxHfEZIa/CeIHxrnuId20wbwINiLfkvhvQAvO675bkN4jjcwT4T5CH/aykHuxnI4p14Xwn/cD3PBHfmqzzgdRtyXoq5LqQfrL1XMl+yuo0su49iZeFeUH2oy3c79m5wvbrqz7fOld/7X6+ku+PEEIIIYQQ4vP4A+ZnBY4= +1438613716.950,1.000,0.001,HISTggAAAN142u1XwQ3CMAx07LgpICEGYJfOhsQGLMF4jMCDhMdJp6Qqqnj4PpZd+85tGqe93h83kctTPrBqU7W6vCQQCAQCgUAgsB9Sxx+Nf7/nSL6S/NF6I3wGtsW92gxxvI58mdjGP0N/Dn6BuJM+G8/U0ZuA50jqHfRRB/WbzrnaA9TP4J9AR0G/AK+TdXDgx76VrG8hz0eIHtqJ3L8RvUzeP1aP8UR87bzXubNvjMR7eWzfMB4l/2ups/+NrLcOzhvWx+h8WTuvRvNk5VzUjXN4a96vz4V/593tvHwDMlAFjw== +1438613717.950,1.000,0.001,HISTggAAAM942u1WQQ4CIQxsCwusevABHvyJbzPxB37UJ3gQLpNMuoY18dC5NNMwpTSlcHk87yLnq3yQutVu7faSQCAQCAQCgcD+UId7/tn4Bv8/AW6gU6JLG3WoX8CfSZzWbSXrBy/gR96cvCvwE+gxToNzDP0B/Ab5FNCPfVbQFYiLdajkvCvJD7lXl0Ls0B3J/onUDeuRSb/gOnH6TIhuAZsIN9LX5vS9kTxYPRLpX3YfvXmQiZ/d863xZ+ePTvp1cu59m6fuNG//9V352Xv1Bhn1BRk= +1438613718.950,1.000,0.001,HISTggAAAMx42u2WQQ7DIAwEHQOBqJc+oH/p2yrlB/1on9ALuYy0IlXV5FDvZQXB64XYJLf1+TC7WkfqPHX2+8sCgUAgEAgEfonp5Ph/OcfRc/4Hkl3obOMsdB1cwIwvWL/5qp0XzDehQ67QbwNfar4KXw3+ZqxbhE4D0y/HF+gX4WcGV+GH55TE+2C8i31k5M1CzzDvou6yqC+eC+slifp14dPF/rLwqfrLB/lcrLdBv6WdecugjyfRn7Yzzj/0Y4N749t7/Oi4o+7j078nbwOrBOQ= +1438613719.950,1.000,0.001,HISTggAAAM942u2VSw7CMAxEYzstRUWIA3CXcjUkbsBFOQIL2s1Ij0TAgoVnM6ozjj9NnPPtfi3ldCkvxMq2si+PkkgkEolEIvEOlnV/5e/Cn8Zz0Fmnf0BeAXG8Yd/2qbKP7juC/050AXmG6Kt8T6IbJG6Irgpv+hl4FJ4gzl7WJ/Bz6Ndx5YP4zVKv8gD5ONgr1DWCnuoeoI8O54L6btAPb9jpPFrnOp3faORN9y2gDw7+Dv8pOu97gTpKY97UzvnlnfPJGv23H8/VfMf+5H18AkFSBVE= +1438613720.950,1.000,0.001,HISTggAAANp42u1X2w3CMAz0I0lbEIgBGIAtmA2JDViUEfig+Tnp5LZIFR++H8tu7mxXjtVen6+HyOUmX/hsdbZ2f0sikUgkEolEYjv0R54t1FViJdCJ9IzoKPEbfFca0XGi2+MF/K5bIV8FfueNs50gXsEfQM9JHxPEMd8Z4o3kw+cT1NHjJ+ijkn4anDvCeeQfwHfSvxP+AH2MgU6DeguZUzZXBeaksP+WYO6czE0l5zXQY7YSXlQXuxdC3oOTemXhXrDg/rN7b4Gurtx/tnLP7bV3t/L3qvNf6tEPKkgFBA== +1438613721.950,1.000,0.001,HISTggAAAM542u1WwQ0CMQyr015LEQ8GYBfWYQ0kNmBRRuDB9WPJ5FQJhFD8sZxrkipt0jvd7teUjpf0Ql4ZK9v5kQKBQCAQCAS+CUQJ3sJEvWxjXSH8IPLA0ZmY/Rfi4V+IQesqsYn/1UJ21ibiNvrOeZqzfsTbi/hDdyc/67H+IPw7+WWyd4q3I66kizinJuq5kL2JukGcbxH7N6H5vvE9L6SruOfZiQeHuU5qH9npGzj9C9EP2elXbLTPzglMzh9Mznl8aG7Oxv21d+lf9oMn0AkFSg== +1438613722.950,1.000,0.001,HISTggAAANJ42u2XzQ3CMAyFE6dNmp4YgF2YDYkNWJARGIED5fJJT06FQEj4XZ5i13+J47bHy/Wc0uGWnigb543tdE+BQCAQCAQCAR958DkDe35G16Ny6plPFnpll0V908ZV6BP0JuxnfKdWyKmf4IfyF6+wX1BPQ7zu2HfIF9hXyOl/FXWpPBrqa2Bz/Mxin5hvRt1d1KvOy8S5NMi9/jX1vyL6UeUz6t+c/E3Yq/4rzr0ywcynijjcv2nn/DDnvnvr8uZc3DsHPzWfvxXnX/3/zPv6AZVRBdU= +1438613723.950,1.000,0.001,HISTggAAAM142u1XyQ3CQAz0tWxEHjRAL9SGlA5ogvIogQdBQiONvIr2wcPzsRx7xxPLcZLr9riLXJ7yge9Wd2u3lxQKhUKhUCj8fh8U5vZTJ/XbkriTfLSoy0leDPJgXgPLeAPqx2Cc8ZwSvkZ4nOjvEF/AnsHvoONrV/AX8Dvw4fWAcwHxlejrhIf110n/GvA2EnfQj3NioEdJXSPzjrp0cL6C6PFkro3Ml5DzTv63guge3Q9Knm9EkL5Fcp+S1LFk/+jB/Xd0H87O+/v3yBv7FQWf +1438613724.950,1.000,0.004,HISTggAAAQl42u3bzU3EMBAGUMf5YyVKQKIUeqGTleiAJiiPDuCwcPnQyHtZFMF7F8vKeGxv7CQ+7MPL67m157d2MX+V06V4/Hh6bwAAf8l043z9l8d97Xx6ET9FucZ3YYv6EvmyzLjMP0e5FtfXYtx7EZ/j+o7b4voS+U8Rtxbxp8h7F/33aL8X9ewn579E/m0wjsy3Rfse9T3yZH/3xf1bivmvg99/Le5nnDt+3OceeedivU3Fes19mPMf5WvF+q/W21LEzcU+qNr3wf4YyXaj/Tt6XvTiedEHeaaibFfWj/I8v1W/R5nv0d+vAM4dAAAAAAAAAAAAAAAAAP+T/2cBAPgeBAAA4EDntE8xJgcO +1438613725.950,1.000,0.000,HISTggAAAMN42u2WQQ4CMQhFoWVapzHxAt7FK3glE2/gRT2CC8vmJz/UuDAxvA2BMjAw0Mz5/riJnK7ypk6pU5bLU5IkSZIkSZLfo4HO/HXRjueF/B8i7rcRP4zjNLA3ks/tBnoh+TuxG0gldoM4/vyBxCukDj/fpxzB++6QZxC/I/izvgyI10FvpO8byEbq7aQfncQxyGNBPyqROFcafA+c60rmpyzOL+YRMrfRHrG9qot12OKe6uLeR/k+vaf0z+5b+bYvL0UPBSI= +1438613726.950,1.000,0.001,HISTggAAANR42u1W2w3CMAy03YTSCtQF2IXZkNiABRmBEfgg+TnpdC0qfPl+Tm79uLiJm8v9cTNbnvbB0Ngbx/VliUQikUgkEon94cT2L+PQ7ve6EHXU+4HdEzf6VVIPdXa7AI/Ev+c9EC7gx7hAXEDdCvXR70z09byz0Is6lsbHxhPRieubQccI/tjHiTx3yI/9KBB/Ar8g+vC74j4woq+QfRJEtxMdLtap7CB6K6nLzoPS7eK8FaJTIcRcUfVj5XyKlfNr65y0nfKyvviP8puYt//+7/gbgiwFyw== +1438613727.950,1.000,0.000,HISTggAAAMV42u1XSw4CIQylhSklLryAd/FsJt7Ai3oENziLl7x0iHFh7Ns0bV8/zEADl/vjVsq5TNQpZUq9PksikUgkEonEL0D+pO5+TwPJeBL0qYSnxP++LzZS30h8g3gDfQR9bVN2yLcFfTXCR92hngX5HfQBcQb2AXlOYHeIcyI74Rn4nawTpZLvguu1IF+D+riPKumzkndIPbgv8f+w/VYIT4if8TrxK+m7BecxOm960C+L82h1HtTFfBLYvzV/5cP43f4CU1AE5Q== +1438613728.950,1.000,0.001,HISTggAAAM942u1WwQ3CMAy0YyekICEGYJd+WQuJDViUEXiQfk46JUXlAfg+p1rxnd3GSc+3+1XkdJEXrLE2TvNDAoFAIBAIBL4J+iN165v96CAn8v+HSMCo4yTfVvrmxgXiBXwc4ga6BfSW9TtSR4X4oaO76O0bH8HP4HkCfyd1ZqJrkG+kv0J8aicvwfMEepV8r0rqr8THiB/bX6P7R8CPrUudvg3YiX/uzJWT+fHBedSV8+Sdfrc6L3XwnJHBuHTOm3+5p3TwPX/qfttKn+XpE9LBBR8= +1438613729.950,1.000,0.000,HISTggAAAL142u2Uyw0CMQwF43yJQNAAvWxtSHRAA5RICRxYLk8aORLi5rk82Ulsx/Hu9f64pXR5pg9lV9s1b68UBEEQBEEQ/A8Df/kxXnb2ZVCDOjLEL6Am+vWPxTxVtEF+9Q9ZP4DdRauc17zTydfFbmKfxN8h7oQ6p8Q773qEfA36pPdusD7g3Yr0Jzv3L2DTnOp70JyYM380z+qvzjlbzJtgvcI++p4M4nn1dCc+9cGrS2mL/6G86PfuG0B/3ik+BYg= +1438613730.950,1.000,0.001,HISTggAAAMp42u1UyQ2DQAz0AbsIIqWB9JLakNJBGkpJKSGP8BppNCiBn+czWjM+1nh9ezxXs+vLvsiNfeO4v61QKBQKhUKhcBz8YD8nZ+Qg+iQ6RBAegNGOdSgdfh9JniS6RnSN3HsC7hvPEGcm+gnyd/BDe4d6MM5C9DPxW8DO+nAhfcZ+N1I3zs0IfcT/kWJOmtCFiB/CjvdR89vFewjyHlzMcxL/hLwh3vkg6gjRHyPn2Lk/jNwrhb+JveI7/X/dp37yXv43zll5/QMRKQW+ +1438613731.950,1.000,0.000,HISTggAAAMh42u1WyRECMQyzld2ELA8aoBdqY4YOaIASKYEH2Y9mNAkQftZHs44tx86xOd/uV7PTw95Ijb0xLk8LBAKBQCAQ+Cd8sl/gN4D6ncQ70dX7UehAcCadtfFC46Bx9gfF7Xykeex6B4pnLuSv7Mxb4yrikmCue6O4Rcy/dOIq2XMnX6V+sv4q7EX0EcKeaV143SDyct9435rYpxjc9xD6vXMAUY/6NpEvd/Kq+lyMq3M3qoPB/n16b/sknVn+o/WlyXpf/w9fGwAFig== +1438613732.950,1.000,0.001,HISTggAAANV42u1W2w3CMAy0TdI0CKQOwC5lNSQ2YFFG4IOEj5MOF7UIPnw/pzi2c7LzOl1vF5HpLE/sGmtjm+8SCAQCgUAg8A4aJdi0DrrST+Ff582//n3EzuI6J+BuH2CcyXpC8mL+DHEJ4guZRzaSv4BuXDcRe+cK8T3fAcadR8i3bzzBfAW/Cv5HUudC+mHAI6kf7oNM6pGdfnl+4vQV94OBLiX9UYeF6ML9ORCd6tiN6FfnHC49H0r6yXTZwnvFPqyfOPm3us/W3rffeqf+9f37tS59AG5PBR0= +1438613733.950,1.000,0.001,HISTggAAANx42u1VyQ0DIQzENtdqH0kB6SW1RUoHaS5lpIQ8dvmMNPLyiFaKPB8LY4bBGHN7vh4pXd9pg+1Wdqv3TwoEAoFAIBA4A/Kn55HJeHXyIgf9QniHzZP6R3whPOpYxm/AU2GciY4KtoG+Bfi7s08BHgP9HWwDnhXmKznfAvtV8K/gH3wXWNdJnhrYDjowT4XoYfoyOVch9y7kvg3qAcfq1K2R+jayzg6+FyX5wvXFeV9KdLL6F8LL5nWyDyjJbyJ+ceJY35KD/cSLm/XLj/vz2f/W7D4W//Km/wsMRAXY +1438613734.950,1.000,0.001,HISTggAAAM142u2VwQ3CMAxFEztNKXBgAHZhNiQ2YDnGYAQOhMuTviwBQiryv1j5db6dxHaPl+u5lMOtPOHD1mHtdC+JRCKRSCQSa0Zdebz6oa5h7cH3Cr4Gtgld6jTwLmyHv4HvQncGvwG/BT8Nuxd5Nfg16CzIZ8b+RZxnQh5d8K/9O6wX5KF0m7gHD96P/jw//Ses+V4u4nbhV6Cr6tFEHfA+Ix0XdU19F33kQZ+ZiFOCfrGgHlUfmsjH3pxT9uU5V9+M9+t5+2//pcS49wfvPwXK +1438613735.950,1.000,0.001,HISTggAAANF42u1XyQ0CMQz0kTgrPlAAvdAKraxEBzRKCTzY10gjR0gLAnk+kWNnfMRyds+3+ypyusoLvq26rXZ5SKFQKBQKhcIvQXfm1S+dZ3w2qdckDpSNyAPkDt+RDWQHO3YuYL+BP+QP4mchfpEvYD2CPfIP0DM/ncS5AM8B6h5EH0l+g9xPkDg6yQf3W1IP9Mfi8IRvkD71yb62pN8t6Xfcd3IvjeSDfJ3E58TO2X8Y0UsSL8vPkrpl82l2btnkfMn49U2eT8/hf3vn9gK+J/oEvW0FXg== +1438613736.950,1.000,0.001,HISTggAAANN42u2WwQ3CMAxFkzhNC+qBAdiFlVgBiQ1YlBE4kHL40pOL1CIO/hcrzve3ZcVWzvfHLaXTNb1h3eZuy+WZAoFAIBAIBAI+8sY6+Utdj+/pfv5/4De5N+Drf7I6OosdhG/ib92OUIfGV9DTukbRn8Q/SvxynoVP8UtdR6m7SR0z3E+g26CfHr9JXQa2Qv8P0HeNI34DvuZPzntTW8HvxQ3wPulMc6X9ornKoG8wL8WZ2+LMF8XZyvlfu99ov5Sd9+dWe3cvvV/nX7v//60/+QWK7gVY +1438613737.950,1.000,0.001,HISTggAAAMd42u1XWw7CMAxrmq7tJj44AHfhbEjcgItyhP20P5YsAxIIRPwTZYk997FIO11vl5SOacBHtBHz+Z4CgUAgEAgE/hn2Zl2mnwUP6y50MvBd8BnPHvQz80Wsx8n72PMCcfb1EeuIjfShTiM60/cB+rDeIN9AvxLeBnkl/jv4wDrrz8QP+nJSXyFfIBrZ30Z08VzZeRipV8g7uWdKn91LvA9J+FbfpZH1FPLfZYLnQr+I7yx/aK6pufDqPPyVef8s/1v3x3ZTfwTw +1438613738.950,1.000,0.001,HISTggAAANF42u1W2w3CMAyM7byokFiAXZitEhuwFOMwAh8kPyedElG1CMn3Y/lin904aXu9P9YQLs/wgTUrzertFRwOh8PhcBwL+TNdn89vdeVLvv/3KYlX4gvxjeT19Qg+8j2vAJ+ALxBfSR+52YXUiRBvoJeBP4G/gMX8QnQqPFcd5HX+DH1gXCK2Qp+Z7C/uD8ZFMv9MdCLUN4jD82KkDxucO5x7IPUS0QuTdY3wkfCZ9DV7P3E+7D4q0WH7oYN7v9Xf6z2oG+vL5Pre30k5OF/elJgFtQ== +1438613739.950,1.000,0.001,HISTggAAANV42u2VSQ7CMAxF7bjNwLBh3btwNiRuwKU4DkdAgnTzJetngZAofhsrTj0kdp3leruInO7yxrrULtP5IUEQBEEQBL+A/mme6kjcTyR+Arkyg96InRdPwZ/Bd7nLCWQhawV/q77Begb7CnY7R1/BvsF+gf013gHWFeIY2GUnLp7jCP4L1KOBfg/+Z1LXyakr3n9x8s4kHtZDSd94/WZOH43qxen3TM7P8k2D/5sRf949KLE3cn9sfhiZDzI4Z2RwLm1lrn/rvdAP+dn6OxsEr359AigyBb4= +1438613740.950,1.000,0.001,HISTggAAANF42u2WUQrCMAyGm3TdqlPwAB7CG3g2wRt4UY/ggy3CBz+biOJD/peQNMkfmjTb8Xq7pHQ4pSdyk9akn+8pEAgEAoFAIPCC/QmPLdgpHX5dL8K/iPiuD0KyrkHEj+AZxP8ozxk3NVlFPcq+Qb4dzpnfoWf4zdBHwd/tW9RRYZ8RNyF+D/8qeFmvi74U6Fn0Ja/sky/4mfAbxbwUMb/sVxZzam/OaRU8Ju7TcN/q3XD+TbxfF3xEFvmX9oSv9Lcv7UX70f79dF97fJfW8T8Au+4FKw== +1438613741.950,1.000,0.001,HISTggAAANF42u1WwQ3CMAxMbCdp4cMAPNiE2ZDYgEUZgQcpj5NONkICHr6PZfdqX53EzfF6u5RyOJUndNo6rZzvJZFIJBKJRCLxPbzuYUEeWgYhvEp4QnhK8qlTz8j3GbyH99E+7YDnnfgSjDfIOyC+kvimdwG/O+8P4Bvwt3x78Ecw/w54HXhK8ijUW0gc130ldQbpi5H90sg6oz5cf+yfd37Qt+D+F1JHyD5nlp0n1NEc/ey81TfnSCX9VKcv3vyIQoL6vflWg3PyU72//h/8na4HDDcFGw== +1438613742.950,1.000,0.001,HISTggAAAN542u1WQQ4CIQykLbiwevABHvyJbzPxB37UJ3hw8TDJpBhXo0nn0pTtDLRA2cPlek5pf0wP2GJlsXq6pUAgEAgEAoFvQFaO+/e8n/9j4AvhqaODekbidXAedeI3ZDyDXvcL2D5eiV6GPAziOm8CazCPAq/rNlIv1C1Er4KfgVdBt5H19nVtIa7B95nkuSN1xTrOoId5TSSPRvajkPzNqbuR86qgy+6HkX0TR5fxDNbH6ifOeffulZF7LIN8cfhC6uT1idE+lQb72Lt9UlZ6B+TDPHnR/7X3zd3XOxZxBRU= +1438613743.950,1.000,0.000,HISTggAAAM942u1WSQ7CMAz0koS0IF7AX3gbEj/oR3vkyIHmMtLIaiW44LlYTtIZO4md3p7LQ+T6kg98s7pZu6+SSCQSiUQi8Qvon+ZnB/dBiTX4r2O8Rsad8CIK4Uf9SnSdrDOwQ6fB+Anma+Ajj4PfwRbQq6A7/Bn8Cb5rsK4B/wV4O/gz+BPZv07yKYSX7bORcxvzZ7K+QDyoi/fJiR47p+h+KomLWSc6GtSLkPgV8mB12YK6j/qBk3MxEo8G9S5BX9CdfedoP7Sd/e9b74m+AZ2IBdQ= +1438613744.950,1.000,0.001,HISTggAAANJ42u1VQQ4CMQgstOCqFx/gX3zbJv7AT/kcn+DBbUwmmdBNXKMJcyHtUKAU6Pl6m0s53csLdZGySL08SiKRSCQSiUTiDfmSXQn2mR47p8F+5LeCVMIL8ErO9bXBfgNZYe1wbgd8tzORuNCegdwTfw7+MB9O7oXxHYHvcR6IPrMzkfw78OweaBfjMaLn5J2E8A68kfrwII9G/LG61MH3b0Q/qmeUFvR1Jf2FfqM+bEF/lkE7GsyF0fkgK+ebrJynW83bT83nreL9lXv//X/9BP0rBaQ= +1438613745.950,1.000,0.001,HISTggAAAM142u1WyQ0CMQz0sckmSEgUQC/UhkQHNEcZlMCD7GekkQNIwMPzGVnxMUm8Gx8v17PI4SZP+GAdbKe7JBKJRCKRSHwCTV0/0bfNdYbzHaljEBfpUTI/IleiB+tVyL8MXkmcwnol+Qrkc2IXsPvgBnaFeAN7F/ivkLcRvw681dmD3g52CdYX4BacdwU/VsfIPTC24F5Z/ynZh072eSF1PNBjk7o90Ouk/33yO5ZJXUr6WoJzY/ojPfKmX/SfeTXe5Lv493ck31fQ9QBgzAW3 +1438613746.950,1.000,0.001,HISTggAAANR42u1WORLCMAzUyk5smBQ8gL/Q8i1m+AEf5QkUJM3OaNZQJI222ZF1Kz5yfb4eZpe7fVFWxsp+e1sikUgkEonEEUCO4Kc5QKwjWHdiBFyFndP/pAueSZ5E/VXwRPEq5VF+newXWt/itpVPQR8zcSPZKH+jOhrxQvkKyZ38N/2Z/EtQ30xxQPkr6Qvl47r64HxB+uj7837qYv9Voed9aoGMID/beXC+XOR3YQ/RD2ieZfD8RPdCGaxH+VnQHwbvHeV/9HuCnd4n1T/+jLP3+4QPN6IFQQ== +1438613747.950,1.000,0.001,HISTggAAANJ42u1VyQ0CMQz0kU1YkBAF0Au1IdEBDVAiJfAg+xlp5KzgASvPx4oyHh9xkvPtfhU5PeQN71a7tctTEolEIpFI/Dc0W/BRP/RH8tPBfBgv0vVBvgdxln0L4pVuJ2INeBX2HdaVrBX0JtA9kvgNeA66GG8X1LOHPGai00Cvgf/idyD+Bfyi/mA8rHMmfCPnhfuVnFcBi/5O5pNZDfiVzLmBH/IKmVuWhwzeJwvuMatLyVzj2kg/LLi3ZeW7p4P1rX2/JDinrf8z9mX9zf3jL/cFBZs= +1438613748.950,1.000,0.001,HISTggAAANN42u1XyRHCMAzUYccBP2iAXqiNGTqgIUqiBB7Yn53ZUT6Gj/ajcXR4JTlycn087yKXl3zhQ+qQdntLIpFIJBKJxC+gWYIldVT8viN+TvynfQ3i43rGK/CcrT2QBfhM/43EdaKvoN+J33nINuQJ4jTwr8CvEx4V9Mink3WB/Trhs4GdA/8deG6EH8pG+oV9Yf1sxN5IXwv7PwnOrwd8KjkvRvRYHwt4Hc3Pgzw98BfCx8h7L6R+SvYvJA7rgwVzJ5pXumj+/eue0YN56iJ++gGi+gWu +1438613749.950,1.000,0.001,HISTggAAAMt42u1XQQoCMQxskt24FhEf4F/2bYI/8CM+zSd4sL0MDKnCImLmEibJZNOQFvZ8vV1KOd3LC9asNKvroyQSiUQikUgkvgcJ4vpmvgzmRfWN+JXoFfImwntdB3/nc7M74F23h+91/xF0BnyBek7yPIhj3gJ9IcfzVYhjfwfgTuZXIa/C+Sro2JydzBt1M+GoY/tgJO6wT0b0rC7uF9tLjE9kz5VY+7BfIf9h7D4Zmb8N3jsN7rUEc7SN3jMZfH9+9Z3+93627l+elPQFtA== +1438613750.950,1.000,0.000,HISTggAAAMp42u2VMQ7CMBAE72wHOwKJD/AXvpUWiR/wUZ5AQdysNHICTYrb5pTNetdnXZzb8/Uwuy72RV6rrzXd3xYIBAKBQCBwRHj0uUnvO3UZ3ifhqapfgvVF8vr7CfgMfl1fgZ8kT/06f5JaZP0sOVX0CfjzWpvwXXeR/cyS32A/uo76reKTZR8Zcgro9bzofArwDeaswrP2TfNjotM5MJgj5Udz5pDj4GODPBt8b1QN5pn68AGv5zq6J3znfbXVx/+8/371Ocr/xD+xCQVn +1438613751.950,1.000,0.001,HISTggAAANN42u1WWw4CIQyk5bXozx7Au3gtf028gRf1CH4IP5NMStw1Mdr5mbSU2YEU2NPtfg1hvYQXYmfprOdHcDgcDofD4fgGyJ/5lA/pC+EBNeoxH4ED+6/snDoXqMsQN4gVeOhWyBfwkQ0W8MXqKugvEI/xI/HTIM6Qb+Cjkv1Z4DuoP8ZXmFfIOoTUDf8H0JVJnwr5CvMixKyfhOgp6VurLxPRT6Qv1KiLk+cpEV01zvfsOsXwo5P52fP/LnTyXpON99je9+WvvJt77ZNsrGO+5AkniAVj +1438613752.950,1.000,0.001,HISTggAAAMh42u1VOQ7CQAz0tSFBCD7AX/I2JH7ARykpKViakUbWhnR4mpEVe+K1vd7r/XETubzkA++snW19SqFQKBQKhcIe0Dr3T/FbdWyjvkI882tgO8QZ0XHCAboG31vCU+cD6Bn4BeQTJM5BdyL/acRGv7nzCfQx7gj2QurzjT+T+hroL0n9WP1n0idP+jSBfyRzECSfAD0nfpLMF54rCCMimX8lfcnuoZE5ZSzJfdXEP9snTvIf1WH7Y3Rf6uBes5327r++b/oGPtsF1A== +1438613753.950,1.000,0.001,HISTggAAANR42u2Wyw3CQAxE195PEsSBAuiF1kCiAxqlBA4kHEZ6WgiIXDwXa732jPfn5Hi9XVI6nNMTebY2Wz/dUyAQCAQCgUDge9ifeKwz9g/1vKPrEKdj8i//nwXqM5mvoLvwjJCn/E1sEZ4mfKPoF4lT3p2Ml7wJ+AeJG2R+D/4Geco/wbxDvQ3WOco+6bqVP8M5KK8Bb4VzLhA/Qf0V1k08vXta4J5l0TPQc7D0bjO8p9p5lwnqSLCv3ukrtrKv2Zt+X5m3db+2H/fVrb9LW+37S+cB7MsFfg== +1438613754.950,1.000,0.001,HISTggAAANB42u1VwQ3DIAzEBockrZQFuktmq9QNukTH6wh9FD4nnRxV9BP5Picu+GwjcG6P5z2l7ZW+yI2lse7vFAgEAoFAIHBGyMn6EKcvT9eD+TLJp8BG8hj4FIhDXWBtsL/rM4kzknci9dbGK/jMwCuJy8Bdv0Jc91nAZ2t8gToryVfBzyDOHH2CehdyrkrOnfko6AW+G1krnHsm9y6TfEr2F+eemuNbgNm7UhIvxE9IX4no6viqMw+8d350DijZL4PmrvxY16j5/u/4Uf8f+QDUrAWW +1438613755.950,1.000,0.001,HISTggAAAMp42u2WyQ0CMQxFbWeWsAhNAfRCbUh0QKOUwIGZy5eeEokLi//Fcub7x3a2Od/uV7NlsRfKan21cXlYIpFIJBKJxDfCf6wOf7P+Xp2A/0IHXgBPdVr8AvED8Eb5rnydt4rOLPxJdKvoFOFXyIN0jjLv5p9kPCR+Lzo7GZ/EbjoHyWuQ+Fb96mt/Z+jzCOtZoY8O6xiQv0HfDfZJwH6kfUJxdC5Iz6Auh7pL53lqnS+HvhCP7gfvvE97x6PhO+T7afd5/Ok75U9JxwT5 +1438613756.950,1.000,0.001,HISTggAAAM142u1WwQ3CMAx04rihFSAG6BBswGxIbMCijMCD5MGhkyPBg0q+zykXn+M0VtL1dr+KnM7ygjZOjfPlIYFAIBAIBAJbQop9vQH/7xLJi6yDcay+DFwgrjj1GfEr6D1+13iCcQG9gl4hbyX+rh8bz6AjL7CPPt6DfnDWnZ36DXwG86gr+U4T+BaIN3IORs5bgQuZZ30jxK+Of7S/CukvIf2Vic+LF7JvI/GZ5GPrsHrSoN+rm91DefAeSF/ea7/Ot/X35V/et49zeQIeRwUU +1438613757.950,1.000,0.001,HISTggAAAOB42u2XXQ6CMBCEd7stUuMR9CyezcQbeCmP4xF8EHyYZFIwoCbM99KwXabTH9pyvN4uZqe7vYih9KFM54cJIYQQQvwzvvF+eSPfJ+q+73+NvAx5Be6RqBeQbxDPjfYLyQui0xGdMb4DXYwHxFleT3yO8UPDF+pW4ndP/FVor8J7Qfx0ZN4yeb+AD5yPHvy1dDLpZ5D1guOViG4i6xO/j9Y6son18WG9k2dj/2PkO0ykdDIeTN+Jjk30MXf/8pX2QZvp0xdud+n9fO3zzX/cr62c40IIIYQQQgjxLfwJVQQFvA== +1438613758.950,1.000,0.001,HISTggAAAMh42u1VQQoCMQxMmnS7CoIP8CHefJvgD/yoT/BgvQwMieAKQuYytJvMNNlt9nS7X0WOZ3nBJuvkdnlIoVAoFAqFHLRaUH3ZsD/65f42oqNJP8YG7ODH9p2c7607Jneil9XH/AUY41fIQ50O+YPoHuD5HvR3sGY6C/haED+Anfjge1vJmsWzPiL3oA4j/bZAV0mck+/LAx0l+43cDw/uA/MXEi/J+jQ4dyO6EtQTzQdJzgsJ8rJx+uHc043m8L/9F/XH/voEYQIFDw== +1438613759.950,1.000,0.001,HISTggAAAM142u1WwQ3CMAy03SQtAQnxZxdmq9QNWIxRGIEH8eekUwIfBPg+lhv7cpekac/bdRU53uSJqUVt0S53CQQCgUAg8B/Q0POT+6SD/Qr/gx6N1CEP61PIE+S5xRmeMz6vL5Ab0bMAf+7oSIR3Bj8Zos9Tyfie+Cwket+J6C/g08cPoGMHeSW+KvFXSF5gvRfgxXWdYH1fPS/Mt5D91E40ogfPXybvRyZ+bZBPOnqN+Ov1JTKvkj4h9aP3B+MZvYf0zXr98L36bd9FfQA15wXF +1438613760.950,1.000,0.001,HISTggAAANh42u1VSQ7CMAx0nIVCuXLjEfyAtyHxAz7KEzjQXEYaOZUoQmjmYsXLZGon6fn+uJmdLvZGXmxarF+fJgiCIAiCIGyHBNbIepRnFA51OeDLhKf7K/Dimu0X+RlPt4XoaBDfQV6BfRvEMX8P/gnqGsS7nUFXgfzOcxzkn0HXAXiRrwXzwb5NwFeJ/kT0VTIXNucazMWDuWA/nZzXSuKZ7OfB/SyD99gIL+q1QM/ae+NBP9j3ZaIrBXVpsC7yr30nWZ9/5V3fKv/b+v7lPytonoLw8fP4AiteBSg= +1438613761.950,1.000,0.001,HISTggAAANR42u1Wyw3CMAxN7HxcOLAAuzAbEhuwDGMxAgdSCT305EI5VMjvYjmx/ew4cXu8XM8pHW7pCR0yDymnewoEAoFAIBB4Rf5zvq3klR0eIfayMk8lcZTos2xDFvCf+SvsI8+sd+KPfHuI10Fv5P+2kDyNxNuB3iF+g/qM+E9gh/GM7BvwTITfSN2V9Af7wvqI8YycX3XOt5D+K7GrZF2c+95gXZw4Su6JkvejpJ5CzteT4rz7vHAulIXzxJsPXj7y5TyTD+fTVuf+r78XeaX/m90DTOMFnw== +1438613762.950,1.000,0.001,HISTggAAANh42u1W2w3CMAy047RNQEIMwC7MhsQGLMNYjMAHyc9Jp8ZVQULy/Zyc+BU7cXu5P24i56d8YI21cbq+JBAIBAKBQCDgh+5sj3Ii+8wuERn1cuPitE/sf7LxRPxkYMzfgGfQT8AZ9CcSv+8fYH+G9S6fGleIU4mfvr6An0IYz3Uk/cB4BeIskIfBupLzsvqyuhmxr2CXSX8yubfsnqG+Dfaf+WGyrdjb4L1nbM73K6Qua3XAPrP6CnlvMpiHd25556NunJP6pbm8NV/dyc+/ffd+fQ59Ax90Ba0= +1438613763.950,1.000,0.001,HISTggAAANF42u1VOQ4DIQwEg9nANnlA/pK3RcoP8tGUKVMEmpFGJuem8DSWr/GwYs3hfDmFsL+FB1K3sVs5XoPD4XA4HA6H431Ew3+VL07yMz8RHoH6RHwhfCOeIT98hbgSPQp8I1+6rd02iK/dLjC3EJ0V6hvER9+O9DXII4+Cjgz1xdCTCf8K/Phd1Dh/BT+DDiU8C/QlMicb/DJ5P4X0Z1LH+GWyDnVUci+V1IsRl8n/TwxeMeKze+LZ/RSNORb/t/bqr/f41no+9b5spfvv3uk78EgF7g== +1438613764.950,1.000,0.001,HISTggAAANd42u1WQQ4CIQwEWpBdPfgAD/sT32biD/yoT/AgXMZMSmKPncukpZSBLWVvz9cjpeuRvpDBeXC5v1MgEAgEAoFA4BcZ2CsfQzHimS2Gbpyn+D9IeOY9Da7gn9zALmQc809/B13V8Gey7jZ4H3whejvs6wy6OqynsB7ajcRX0LOBrkrOoUG8kHiF/DvsS4Hx/FGvkO/YyLiSOlNSn0L0MD/mrcTP6rYQHdWoV7xPGKeL/cJiK28x7veq7dWv1CnPal9l4+LctzOpF+/+7v2e/KvHnP8BYuAFHA== +1438613765.950,1.000,0.001,HISTggAAANN42u2WzQ3CMAyFYydpWiRgAXZhNiQ2YAnGYwQONJevstxCxcnvYrl+/okbu73cH7eUzs/0QZ6lzFKvrxQIBAKBQCAQWEJ25q3li6HLRr5+mUeN/0bayauIR/8Cuzr5Gp4XyAG8Ar3XM8E+Qk7gZ9TV7UcjfkU9J+gj/Kg34zwT5GDo7EuGnefq8Q+OH/teDSng8/0Wo6/q1J+ce1ehN+PeiJF/cOxi5MnOvKhTtzfX6syNOPF05fxnZy/oyv2TNvK27qm98v26t//1fVns5TfsowWd +1438613766.950,1.000,0.001,HISTggAAANV42u1WSQ7CMAy0szcICe78hbch8QM+yhM4EC4jjdxW6QnPZZRk7EyaxOnt+XqIXC/yRRysg8P9LQ6Hw+FwOByO+VBgIe1AxsPO+VgeNm8i/YH9PxJW0GO8EH0DH8nwkUFXoL+R/p++Akfio0KeCvnO0F4gT4V8ffAJ4jr4iKBDHwXm66BbyHfD+Aacid9C9j8Z+6ZkvIEuk7hM7lFa6QPPeyTjwWircb6V3JO48T6qUT/UqBdWfNiZTzfWnbV1TCfX1b06OdiHTF6/HLyuv32vP+SVBQs= +1438613767.950,1.000,0.001,HISTggAAANF42u2XzQ2DMAyFYzskcOsA3aWzVeoAlbpoR+ihnD7pKYA4VJXf5cnB/3ECXB+veymXZ/kiVraV/fYuiUQikUgktsGyBYkT5scG82QH/Sn4wL5uzNMHcdvKM+SAfYXc4G/Ceoddgzwh7iTidujR7yzyqmDD8wXxOtZn0R/mvQzqbULPRd20o16FnwBXURf7EGI/Qvx/kLvYfzU3IeYyRH6s20V+jOcDf6ouNS97z62J8+sbz6XvvG8CfSo78zx63/3Le/nXvw/sZP2z67UPFiAFbQ== +1438613768.950,1.000,0.000,HISTggAAAMl42u2WTQoCMQyFk2ba+QHxAt7F63gNwRt4UY/gwnHzwaMzIoKQtwlJXpI2bUNPt/vV7HixF2KVvspyflgikUgkEolE4v/gHX8RPOd/sMMzwVf2AfoIveBf+uZX5J2Rv0IWYed6GuoE+AFeRf0F/gn+wD4r9Bn2A/I02EeRb4Icxf6qiB9E/5tYZ4W/iTzq3F30NTben+j4F3GPSme/Dp3xTfRRxZuo66JPfD/R4W+dA7Hx/drG97933uyt+624T+ek22/gT1ksBVE= +1438613769.950,1.000,0.001,HISTggAAAMV42u1XWwrCMBDcR9K0ingB7+LZBG/QC3kkj+CHyc/AuCJFEXZ+hmyGmXSbpPR0XS8ix5s84Z21s53vkkgkEolEIvEKmi34av/0zXrkaxu/RwdfZCVjJfqpcwH/Broa6LE+kZwKzHIr8ZthfowX0A+fHdTxORbgBvN7YIfcBjzqB8jFfCP9q7BuC/ptRFfJf4eT/VBIjgc+TnwinZJ+sD4JWbcH+94DnX94bgphI+ef+Ub3jAa+/3oP60b34a+/i/oAjdkFpQ== +1438613770.950,1.000,0.001,HISTggAAAMx42u1XwQ3CMAy045gQ4NEB2KWzIbEBizEKI/AgfE46OQ8qQPJ9Tq6dsxM7VXu+3i4iy11esME6uKwPSSQSiUQikUj8Hgp8t7HnyCWwUaeQOCPxEuStYL91duBncWgbqa+RvD64Q17kDryQOCfr9lBHhzoOZN8NdI/gx7wN+BTkZefaiL6TfmLfClmvpE8YZ8Hcsbm1yfVK/ncYO5lrC/Sjc66gV4N765P3Vsk9Zvbse0JIvRFYPRb4Z3U/FbcVvp1/6/r0z/avT/zLBb0= +1438613771.950,1.000,0.001,HISTggAAAMt42u1VQQ4CMQiE0m531Rgf4F98m4k/8KM+wYPbyySTMb1pmAuhUIaWQq+P593sstoHsUvfZbm9LJFIJBKJROIX4X+at0/uK2LdSTwn/gvYg8Rzwj/WK9hRNsITYK+wPvI7gh6EZ4E4Q3bCi/4n0FfYv8E+lAfif4ZzdIi/kTwX8MPzG7n/DvfK4rrwb8DTRB0q+BXB20icJvRCzh3i3QZ5j+zdmugflocTu+pPrGsQqfJx0ccmeE30vX05h3xy7s3Ox/zfSF3e1N0E/g== +1438613772.950,1.000,0.001,HISTggAAAM542u1XQQ4CIQykBeqixvgA/+LbTPyBH/UJXsplkknNblYvncuEtpQBShNuz9ejlGtxVGdx1vu7JBKJRCKRSCS2Q/40L2Il8yr4JdCDcR3sGuRvYG/gr8TfiC70Tz0HGM98g/gb2BeIH8RuzkfgGXdxPgOfYF2DMa7D9jUgrwXnjP+BBcYG99GDe1bYj5Jzt6De2f1Wsn4n/5pK6j2qN1bPQs5Pid5O6ljJe2T7jt6TBDowPnrHurIPyZd5dKe+uLZvlh/n3UvnZl0fcMoE+w== +1438613773.950,1.000,0.001,HISTggAAANJ42u1XyQkDMQy0JB+7OSAF7COdpLZAOkijKSGPrD8Dg5wlsHloPkKyNR4ZS+Dl8byndLmmD2y1slq9vVIgEAgEAoHAP0OijiFeJetG9guJG/AVsj87+YXow7y+r8H6BPk9XiEP7Ql0ZOBXcs4MvoLfiMV6D6BzAv8I+Ux/5zmD3hlsc+qtJJ5JHHkK3IdA3Mi7wXtEXiX/k1F+gfoqxJtznpF3rcR6urE+c/pSSX8Wp1+F9JUQneLMHxmcC/rlHNk6D2Xnefpr3bKzrs18bz2LBSc= +1438613774.950,1.000,0.001,HISTggAAANF42u1Wyw3DMAg14E+cHrpAd+lslbpBl+yxI/RQ5/KkJ5xTJYt3QeAHBmyT3J6vR0rXd/rBhpQh9f5JgUAgEAgEAitBFslTQXp2jCuT60LiM13Afuh5yAL8DvwK/AZ6Bv8N/BvEKfCfq7BvJ/t2ohvRjdTZIa8L8DeS9+G3w/pOeA3qK8BrJF/0M/DPYK+kXuavwKvkfDK5v9lZF3K+RvqhpP94j43UL6RvyemHOO90Ng72g703deaJNyf05JySk3NE/jxHZ+frKt83av8CbbIF4g== +1438613775.950,1.000,0.001,HISTggAAANZ42u1W2w3CMAyM87BDi8QC7MJsSGzAYozCCHwQEDrp5BZRiQ/fzymOc7ETx+3xcj2ndLilJ8pgGZxP9xQIBAKBQCDwCYl8NtWVlTqyUuf9n0f8MowLWY86qNfIfAbdCuMXFOze+gn27YN3YK9EZ4J9UUchv3nwHvZR4A5s4N+Jv4EdxzPEY2BXkjeywXmYc14N4q8QRyH31oDVqSN236zeCqnj5vjrwjpndcPiYu8H581ZX533yvJPjj07fUOcdbJQZ6v++Kv++m08srB//sv3VB5qfwXF +1438613776.950,1.000,0.000,HISTggAAAMx42u2WSw7CMAxEnU+TFBZcgLuw7bWQuAEX5QgsqDdPGhXBCurZWE4cj+1O2p5v96vZabEXymrTavPlYYFAIBAIBPaJtDPeX30u6cs47mdYX6/w/b9xQh6u83yBX3HeeWfsN5xvsCq/19GR3/2DyOd2iL46eAb8o6i/I558zF9E/IR+muDhvGaxruZMniHm3UQ9VegnoU4TcVXo04Rei+iDela6yUI/6l6UjfuTN+pUeU3w2MY81D1Xc1N9fPo+fpf/X79r1Hd6AvduBSg= +1438613777.950,1.000,0.001,HISTggAAAMh42u1UyQ0CMQy04xwLD7YBeqG2leiAJiiPEniw+Yw08kLy4OH5jBzbIx9JrvfHJrI+5QPbWXdOt5cEAoFAIBAIBP4HetCfJsUr4R5fiK4RxvwKftQ1sLu/gV4m9TTIy2DjeSV2Ab1K4jufwMY6zmAvoNeAV9Dt5xfIx7wF6se5ZjJf7B91Mtljg7jq7J/tOzl7N1KnOfsu5J4noqtO/cWxjeiYE8fmhO+yfPlu2fu3g/+MTvqv9Ec9GcyTyX2NzmO0D30DpncFkQ== +1438613778.950,1.000,0.001,HISTggAAANF42u1W2w3CMAx0EqdJCkIM0CHYgNmQ2IBFGYEPEj5OOjkgISHh+zn5EdutY7fb9XYROZ7kidQ5dI7nuzgcDofD4XA4fg+v/zXDjn7JsEeiV4ifQFbQYz60L8RfiDzOZ5AXqDcbz1Mg7/CvIBfQY717Ug/yiLOSfEryraBvnQ+ddyROhfoU3sMC3ECuRv8L+GNf22S/M9ErOa+kHrQHcq8iuffJuPeB9JnNzey8qiGzOQ5kDpk9GHFn81j+7yJ+uO9kct99a9/+2/dl2v8BfpsFKA== +1438613779.950,1.000,0.001,HISTggAAANd42u1WwQ3CMAyM7SQNLUgM0AebMBsSG7AoI/Ag/Zx0cipVgofvYyU++xLXtbI+X4+Urrf0hXUr3er9nQKBQCAQCAQCx0Gctbfv5WVxSnhC/Ereh0Z4aPNg/MYrcN4CfCM6E+SbIL7CWsg5C8RX8FewjfgXwt/0znCfzX8B/VO3M/Bm0G9EF+uwkP0MeSqpt5DvZuSeHr+AVcLPg/2G8Y30nZF+MNBTUocJ1snpczxfGayTOnnU+f9lsE5sXpgzH2TnHBIyj349f0fnrRys929xu3U+DBAFHA== +1438613780.950,1.000,0.001,HISTggAAAMx42u1UyQ0CMQz0kWO1fCiAXmiHNpDogEYpgQd5jTRyuPLyfKxNxuNx5PXpdr+KHC/ygo+oI9r5IYlEIpFIJBKJz6GL+TaZZ5DvgW4ZsQLfIDrRV9BRwtvIvRN9/C5w3sB3J7FBPuYV4gPfZSc6G/AqqXMAPvrayb0H9Trxq+RciM9O3ht9YF0hfPauTuYF/VowV43MNauD81SJbiFzGM2tEL4Snxr8jzaZF0Xsa3Z/WLA3/M2+Zvegfrk3f73H9U+6srjP1fX0CXDfBUA= +1438613781.950,1.000,0.001,HISTggAAAM942u1WUQ7CMAiFQu00MdkBvItnW+INvKhH2IetH8+8sEQTs8j7IawUHgxIL7f7IjLP8oR1qV2W60MSiUQikUgk/hm6E174jnu954h9ITq7XyGOkPjIw8AP043EYd+d6MPvocsj6EM20CewryAbnKMd1qsRPw7xkKcT3g3kmdQn4m+kbk7q3Ehe4/xE8ke/QvJEntjfDnVw8r+xr5CHkr60YL5K0JdC6llJXA32SiHzacG8bt1XGuQpG+NIsE/2tj+/zffXfvRDP2/3V4YhBPM= +1438613782.950,1.000,0.001,HISTggAAAMt42u1XSQ7CMAy04yR16YUH8BfehsQP+ChP4JJeRhq5RPSA5LmM7E68VJab3p6vh8hVBmywDi73tyQSiUQikUgkzoeCXU4+x3Qdnhs5p8CVxGugM+BG8uG9dIE8BvZlsIN/Af3e30rqbqS+Pe4GcTCeg99JnexcA/8KfaCOsZN+kJ28zw55N6IrYFeSp+N/RmAbmUcl82EH54jND85hIXkjPeqifiupM+JC+pagLjsYV77cK9Ee0sl9JqROmYwvk/39Ks/ffI8+b4cE+A== +1438613783.950,1.000,0.001,HISTggAAAMp42u2Vyw3CQAxEvd5PAiQSBdALtIZEBzRKCRzYXJ5kOUGROOC5jCbrX7K2c3k87yLnm3yQO6fOen1JIBAIBAKBQOD3SIbmcwV7ftZ5gc6OfwaroenXOg9GnAWHzhX1HWE/wp7vM8CuGnEXPqHOijg8n5C/IV5D/hl6cnhAHbOTL8NPoQv8R+f+GnSFLkYdvAcx4lj9ZvWPOixOH2YnH+3ZL+LEVWO+1JnD7GhvftfqtXWljXuqbswrX+6rvffqv/9X9vpO6Q1LcQVI +1438613784.950,1.000,0.001,HISTggAAAMp42u1W2w3CMAz0o2ki4IMB2IXZKrEBCzESI/BB83PSyQ0CqUi+n1Pc8yOuE+Vyuy8i54e84SvrynZ9SiKRSCQSiUTid9Avx5v6Ow7iR4z1GInjYMd8TuKXwG8KdGifIV8DfbefwF6Jbg50Pe8R9MgFGL83WB+gzkq4AeO+K1mzPmlQt2/sF/4n7COykrkyMkdO/JlfCeLgvtAuxF9Ifg/mX4jOBs+pk3m0jedPg/57cC9F94YN6kfvR5V9Q/+8rk/rt73u+wXODgW3 +1438613785.950,1.000,0.001,HISTggAAAM542u2WwQ3CMAxF4yRtSLkwAAOwRWdDYgMWZQQOhMuTvkxFOeF/sZLYPz+u6+R8u19TOl3SC2VYGzavjxQIBAKBQCAQ8GE/4rEv9zG+72AJNV/xXsyC1xz/inERumaMJ8xPIr4I//d8G7ZDT8N4GfYAviPWO/zIQ70N8VwnT4P+LnQxnnnpTn4WobuBpyE+O9+F+7EuvHqoor68elZ1WMR/pPYjbxHnnoXOIuJN+NlGm0T+zOkDW/uP7dSP9uqrtvFceyH/yb3xcT6esHwFGA== +1438613786.950,1.000,0.001,HISTggAAANB42u1W2w3CMAy0HZcmIKQOwC7MhsQGLMNYjMAHKR+HTs5XhcD3c/IruTpulNP1dhFZ7vJC6ayd7fyQRCKRSCQS20CzBYkN5+b93iN+DfwYN3hPFpKP8bXOyX4OeRPUOzD61/wKtoP/CPYM+TvgStathJfOrfO+8wHsRnQ0st8MuibSl0rOpUDfWb6DDtzfQQc7X2QN5sXIegV0WlCP8UrmDPtopA8azGMheUr0SPC9RtYVco7s/5bB/sigXhmM64/dp/plev71PfIxr08/BAW9 +1438613787.950,1.000,0.001,HISTggAAANd42u1WSQ7CMAy0naWBwoEbB/7C2yr1B3yUJ3AguQwaJVKlqkKey8iu19Sx8lhfi8jtLl+EylrZnm9xOBwOh8Ph2BPq9Q3lH63DBuPoYJ5A9C1PJPLPOxPk9r0QfeMJ5AR5EsQ38MvED3kiMuoL5DlXvoL9CfhSeYZ4c6ffAn1k0lcBOYGdQdxI/leBuvHccF4isTOSR4k/0+M8Y73Yl5C4RubIoN9I5pHNVyCciT+7R2yOhdxHG7zH2rHfuh9Ynt4+Y33qxjqPtv9NjoG9zuvv3icfT8kFEA== +1438613788.950,1.000,0.001,HISTggAAAM942u1VyQ3CQAz0sRuy4gEF0Au1RaIDGqDElMCDFY+RRiYRK3h4PiM7PsaK41xu90Xk/JAXvLN2tusqiUQikUgkEolxUGLrl+pt7efEj2ydK9gOfrQxv3SeIB51FPAfiI4K8YX0Q65Qt5E81meGfIe5Cpm3Qd6x8wmet6C/k/7or4Tx/RViG+ifAl3RHrA43FPUiftqZL886CdBvJE+TK8H9YTUF4hn36sFrMEd0Z1sRG90d/TD+XXn/dMf3eet+TpI1+j5/+3/+PY/AUcoBXY= +1438613789.950,1.000,0.001,HISTggAAANR42u1WyQ0CMQz0kYNlP1sAvdAG7SDRAY1SAg92PyONHM4H8nwsx0dsJ3ZyuFzPIstJHvCV6krteJNEIpFIJBKJd6ABn/hMXS3Q02DdgvNhesr+kUAr6G20gLwSeQF/LdCbV7oj68xu099DPAvIO9h18FshzonYT6A/g7wB30n+rK6N8E7Or0Kd0a4Se6yHk/o0uFed3BcDPxiPkrwtuIdoJ4N9UAL/2A8lyEtJ37I+ddLvHvA6OEdG43h2fvvg/JIX99Mvz9Vfv4dsf/uXd+sOe9cFNw== +1438613790.950,1.000,0.001,HISTggAAANV42u1Wyw3DIAy1MQSS5tABukvX6DqVskEW7Qg9lPbwpCcnl0SV/C4Wxj9sbLgt61Pk+pAPrFPtNN1fEggEAoFAIBA4HkrWHv/3jwO6124i9hT+jfh/zE4cjG+EP3RaQO7rp8F+Bf0G8pXojcQf6g0gP8M6A710OoE+2h0hrgb6M4l7grxg3Hi+CvuJ5FfBXyV1YfXKpK7m1N/gHlaIp5A8q7M2cn+wP4ojl0i/iNN3RvrNNvabbrTP7AjJt+ycK+y84uRJD5qTZ83ls/z/3bv2BgzYBVk= +1438613791.950,1.000,0.001,HISTggAAAMx42u2XMQ7CMAxF7SZpqCgSB+AurFwLiRtwUY7AQFi+9OR2QGLwW6xU9vdPY0Xt5fG8m51v9qGM6CNO15clSZIkSZIk/8cUrD1YK/odSFH7kQ+t+z6vUD+BD/VXQadJnGXd5XkX3QI6Req0/gD9TpC/jLhK/lHyNa+Dr1V8LHAOFfbTYX8z5DfpU0C/0v8FRBf9GsxLg/pojjzoTzq0L53/Fsy7wTkW8KPzb7CfyL/qbtUz0LOd90t0n/wK33g/2U5fHpxXMt7PG/apBS8= +1438613792.950,1.000,0.001,HISTggAAANh42u1WuQ3DMAwkKdmS88ALeJfMFiAbZKmMkxFSREpxwIEK4MIFryGoI4+UoG97PO8i60u+SM1qs3Z7SyAQCAQCgcCRoDHfv+J0UEfJf/D3L4R4g3jk52Yn4LstwHebIS6DRd2Z5Cfgu1/Bx7xC+q7AL5DX/TOpl0G3AF+h3wvozpC3wnjXv5I6C1nPPr8TWS8j/bP1nyA/Ez0jfaizD8zZfzieBvMS6UOAF3JOsqNjzjnM5BziPlYyHyH+6D3g9ZsGdRivTj3vPtOd7sOjvzO2c339APAwBc0= +1438613793.950,1.000,0.001,HISTggAAAMh42u1X2w3CMAy0nTShFRIMwC6dDYkNWJARGIEPws+hk/3BS8j3c0ru6lertD2czkeR3UXuKIN1sK1XSSQSiUQikfgE9M/7szf3r8G8ng/1x/fhNLjCGv2N5DXCGM8gL64b+AvRO8Svjo75OvE14M3gmegLqXsP13XQO+hbUsfizAH7FzLXGfoxp58efC4mwubc9+rsK9Hxf6aQuTGfAlfiM2ffHL8F8xvxexw9Byx4TmkwnlePV+erz8Fvv1f0R+I8zeUG0JkF2A== +1438613794.950,1.000,0.001,HISTggAAANF42u2X3Q3CMAyE4/y1ASQWYJfOhsQGLMoIPNDy8KFTqFClCvleLDvni+vEkXq53a8hnB/hhTRbm22clhWHw+FwOByO/4btTEchdnwigRdX6ibxXYwnrBfEK/wsfINPHeoVYavIH7DeEG/gH1BPRryJ/chvog8V+y92hK3QG8E/Cf0B53kU/S5in0H4CXkR+UGcL++Z4hdRnwnex3+MuI/UMTEfUdx7E3OVBW+tzZ05tU5fTfhpZX3hyzp67579+D7axu+q7Vxv63re/X0CGGIF2w== +1438613795.950,1.000,0.000,HISTggAAAM142u1Wyw1CMQxL0vSnB0zALsyGxAYsxiiMwIH2Ysn0CSRO8cV6chO3SfPU8+1+FTk95I00WAfb5SmBQCAQCAQCn6CL78C+uv0aZ1/6rPIpcCLvRtTbYp2CTx5cgGe8A1eIc8g347fBneid+BbIX4E72ecG56+wv5nvQPwqxDvoR/BvwJjPoa8O9XdyvkL8G7kHuM5AN9LHDLot7gdbl0l/C8nH7q2RuUiEBfwTmUcldVFyDif6ao7ZfMlOffVfN1I3Jf35N1j/7AW/swXM +1438613796.950,1.000,0.001,HISTggAAAMl42u1WQQ4CMQgsFGyTXnyAf/ELfsnEH/hRn+DB9TLJhLrVgwlzaSgzLF0I7el2v5ZyvJQX6rbKtur5URKJRCKRSCQS34cs+iO+fqhjeoF3IsKAX8FW4CHfCe8A8Qzst7+B7eRdi/4B+wN0neTp8N1O8nKwO+gG2I3wO8Qzku+sDv+bkHMo7GM9nMS3oN6sH7BujfQp81fSx6irpF+F8CXobyM8DeKUIC7LS4NzKtFF82GvbjaPWejiPNuL1Xn56/n+9/fdE/v6BUE= +1438613797.950,1.000,0.001,HISTggAAANJ42u1XOQ4CMQy0EzvLsQUP4C/b8i0kfsBHeQIFSTPSyIsQUgpPY63t8RUryl4fz7vI5SYf1C61y7K9JJFIJBKJRCIxD/RLPyW8gu8+sFvAN3g/VpLfIP6B8IbdSV1G/IdswPOgzgZ+o64T6Mf30uUK/ME7A28FvZH4BvEa5EO+gR31DvYCeY9k3k7qMuizEf8F5uvkvDQ4RyYliFt27hPucQ32XUn9snM/nfxneZCfzU2DviXwi/qL7hvWf3QPlZ35fr33/oUic0MnnZu+AVzeBT0= +1438613798.950,1.000,0.001,HISTggAAAMl42u2Wyw0CMQxE43w2AQ40QC/UthId0BxlUAIHksuTRgkXJITnMlrLHttry8rldt9DOD/CG6mzdY7XZ3A4HA6Hw+FYgXkfP1WHifdfEHb6D5TxbgRnxBXEJ9ipU9T7FPoV30fED/uhc4O9wb8KP+oMvw1cEF8Rt4n6qqiDfBJ9qjycQ4NfFjrsN4n6OXc13wZ7xB5FsWdZsE32NMA/iTwm7FHoz/Ixb5zo5UldSscW/+MMcfHu2If3yRbz/ct9ty/VN53LCxA7BcI= +1438613799.950,1.000,0.001,HISTggAAAMd42u2XSw7CMAxEYydNW8qCA3AXrgZSb8BFOQILshrpKQgE4uPZjBw7zsR10na/nk8p7Y7phtzYGvvhkgKBQCAQCAT+GfZjeh/1Z/heVFZ/kfGx8QBxJv4sdunoqY0niKuSp8h4FZ09HsRehGfY/wzrb4QXWWcLOhzqpPYoeqieRfIWyJvhuVWYrzq1Dxz0JPA79CnFVehXijewqU40v9D/Vuc8WSfe4dzea+dOnHfON90Tz96f/mF5vu09YS/S+6592hW2/AVf +1438613800.950,1.000,0.001,HISTggAAANR42u1WwQ3CMAy046SlggcDsAvrdA0kNmBRRuBBwuOkkxPa/nyfU2Ofc6lTq7fn6yFyXeULq6yV0/0tgUAgEAgEAoFxaGdc/6z3+18DVhJndTxdb9wIt/wMzHQTyVeia/lz5QLPLW+B5wTrmegn4BPEZ1hfiL4Q37jfhdQ9Q36BeHb8sPcixA/2wSCeSb1CzptI/xFG7lcZ1Cvpa3J8sfvNWJz7if0W4k8Jm/PdGumTOXNAOudEcvx5+dI5f2RQpzvN4a3z+6j9j6q3t5/N5/kAT94FaA== +1438613801.950,1.000,0.001,HISTggAAANl42u1WyQ0CMQz0kQTYgKAAeqE2JDqgMUqhBB4kPEYaZVkQ4uH5jOx1PNbGcXK8XM8i+5s84Y21sZ3uEggEAoFAIBDg0D/TR9vgnecL8yl5LyroIHckWF/Ieic6CbiAXcHuWAEnyLsGfyZ6RvJsoZ6p8abxgej0+B3YBeqooJuBsR6MK+CfwG/gT0Qnw75UUi+ykn5g/eKDONYv+D2TvkskXgf5BfZRSX4jfWiD88LyKTmXTurUmefaB/NiNAeE/B+dOR/1y/P23Xz2ob4urPPX982r3gdT4QXH +1438613802.950,1.000,0.001,HISTggAAAMV42u2WwQ3DIAxFsRNMmxyyQHfpbJG6QRfpaB2hh8LlS18mbVEvfpcvsLEtcAiX231PaXukN1NVqarXZwqCIAiCIAjGIR/6y8G40qmM9k5UmFewz+RdifaZrF+duEbmzyR/0wzrDda1fFvVU9UCfiupo4AajDNoIfZC6mv1LDC2zriLcw54nubsu0J97JwwTiZxtLNP0C+RPlPSB5PT/5n0Pfqps4+sbgb6Sef3x+o6em/Ij/KPvidH5/n2fv7bf+QFukEFnA== +1438613803.950,1.000,0.001,HISTggAAANJ42u1XwQ3CMAy0naSk5cMADMKvsyGxAYsyAg+Sz0knp0UCCXwfS/b54rquo55v96vI6SIvpGa1WVsfEggEAoFA4L+g0YKf7L8RfXXiCeLdFqJvkJeIn/FmiGfCn0k9BeI9fwJbiD+DraB3BH4luqjf85ZmD5BfyXkF+KyuxdEz0MH+FqcvLI7/Dxn8E5w3kbnJG+fHm18bnOME56vDw3nzvhdxeNgndfpqzvOP7hUl70FInd4+MYc3Wp9u3IP65j7cu591Zx2ful+/dY/rE09sBTo= +1438613804.950,1.000,0.001,HISTggAAAMJ42u1U2w3CMAz0I00L/HQAdmE2JDZgUUboR5Ofk05OQQgh+X5O59qu49i5Pp53kXWVHd5YG9vtJYlEIpFIJBKJ38MO+uuHfgasoJ2wAjvRnQv5D9pn0BX8u56AC+EKeSeoC/Ogf+cF+NL4BH4Yfwa/hWg8P37H/JXU50RH/cL7moP+GNGFzDFqJ/OG9bB9wPrZ3BaihcQZyROdz0k82zcN7BbsjQb2aP+j+FE++u7oYJ/0zXfu29A/zT96b7oBV7gE8g== +1438613805.950,1.000,0.001,HISTggAAAMd42u1WyQ0CMQyMPYQN2Q8F0Ast0BISHdAoJfAg8Bhp5DyQWCHPx/KxHttxoj3d7tdSjpfyAoa0If38KIlEIpFIJBKJ7eDznxb4WUYA6S74XPgZO/IvQ1bSQfb3d3vSOa5SHWxvJBfKy/kb2auIq6KuRv414OmCbxV1cz2d7F3Mt4r+TMzjIOYFknz+fD6gvnlvIPo3kReCR+2hBfmi/cbk3ru4NxD8Pqmre+RBvRbUr3jU/DAZ7z96B7f6Pv8L37fy2xMbUAVA +1438613806.950,1.000,0.001,HISTggAAANJ42u2Xyw3CMBBE17/EhEg0QC+pDYkOaJASKIEDyeWhkSEW4rJzWXl37JmsHUc5X28Xs9PdXkhrDGuMy8McDofD4XA4HP9H2MlT8yLiXiSMM3QDeAUxwQf9FuF3iwPGtaGTkZ9QH5Gv0BnA29Y5gl8Fb0Z9Fjoj/E7g0y91suh7gs5B9DmIPmbMr8hnsa8D/zPEftOfOrfkFcHj+UvifJrwzeeOoh5EvaWbGn6Vz9h4D62hZ431VT1+yf9Up/f+C533pnX66/X56+/C2z48AVSSBcE= +1438613807.950,1.000,0.001,HISTggAAAMh42u1WOw7DIAy1MRD6GXqA3iVnq9Qhey7aI3QoWZ70ZKgqdfFbnmzMwwZDcn/uD5HbJh9YZ+2c1pcEAoFAIBAIBP4PdWxvnk3OV/wvhHFz4pg/k7jD3zoXsk4mOod9AruAbu28AJ+JzgJ2g/gKdoP4QvJA/7XzBfy4LtaB9VSik508leTF9kXJORjhPHh+Xv+kwf5LpG+V3ItE/F59TNe7N6jv3cM0Wa8O5ieDfpt8b/TL8dn3TZz9HtX15s/mE/jRd+8NFFYFgw== +1438613808.950,1.000,0.001,HISTggAAAMh42u1WSQ7CMAx07DYhcOEB/IW3VeIHfJQncKC5jDRy2wMCaeZiJXbt8Rb19nguZteXfRCrLKv0+9AIgiAIgiAI34TvtC9EMjsWb4Kzk//ELB5+N8M5wK6Cn8GjgR55Btg10KOcQQ6/J7j3nfYXuEc+Q9+J3RnOQfKvkE8n9WqEryd9wLpi3XrSj0r8eBIP543NE/YBeRYy75HkP5H+x8Y98CQvI/kVwpPtrSd7bInfrfvL4vnB98WSOL+OIj5/UZfDebwBv1QF0Q== +1438613809.950,1.000,0.001,HISTggAAAMZ42u2UQQoCMQxFm7SdGRXBA3gXzyZ4Ay/qEVzY2Tz4RAVFIW/z6TT5KWk6x8v1XMphKQ/qUBvqp1tJkiRJkiRJfg+HmlCVF8UZ4hvWFepiX8Wv2sW5Kup26IS4GfuG/Bl5TZxjjdsO3UC78FuwZp0Jvh3198KX/i78mugXv1twDwb/nfBR99nE/bSg7x7MkQV11Ryyz1Mwzy7mXr2v+uK7oF8P/FmHVNFX5cNzlCfff0H//M18FVeC/9GnsS/n/Qt2B/zjBPw= +1438613810.950,1.000,0.000,HISTggAAAMJ42u1XwQ3CMAx0HMcl5cEC7MJsSAyAxKKMwIP2c+jkIFV5+T6WE/vOTR1LuT5ed5HLU76omy2b1dtbEolEIpFIJBLHoUzW0UBXSRzmj/KyvArWiL+jkbwG/Li/QNwJfId8J/wV9jvwd+DvRHdfX4N4J/Wj/pnEY70rnGsDf4H/5RCH8UrO39g7Anid8ER9VoI6ldRjpE9tsO8NfPZ90f2IdMrg/WO2kvsczZ3yp43ql8H1o+ehTp6zs+b3j+4HfFAFYA== +1438613811.950,1.000,0.001,HISTggAAANd42u1WyQ0DIQzE5lhCFGkLSCH7S22R0sE2mhLyCHxGGkH2kPLwfCybwTYGW9xf69O5eXFf+CqlSn28ncFgMBgMBoPhfyA/2nFdBtcV/odKeIH4Q34EfibxIsRt/ATrEfYl0HPHjwBvInGuJK8M/FLlBeweZARegfhN3oDX5AznKaBH8q8P4N9DnhPwAqlb7vAz6ELqwOoSSN6JvDchefTeK8vLkXsW0g9K3rF2+lOJnfVhGOxLHYwn5Nyj80E6fo+aW1vn2dY5eta+o+f1WXntPtcHMWoFOA== +1438613812.950,1.000,0.001,HISTggAAANl42u2WzQ3CMAyF4yRtKFwYgF2YrRIbsCAjMAIH0suTPqWhBXHwuzzF8V+c2Mrldp9DOD/CG6myVY7XZ3A4HA6Hw+Fw/A7WKY/6fwP92Ii71k73M/wjE+SVZa08VB5lrXL1u8Qron8QXuxPEGfRO0JclRewi7KfxG6CfIr4KaI3gf8i9R2gLgb3kERO+iOcyyB+hHwyvK8M73NovBs6D/XHCPsZ8jTw22IDOdWN+oc4QX9ao3/jh3Opd670zpu95mXo9L927trGPLeeN3y5Xv+a19552wtpNQXJ +1438613813.950,1.000,0.001,HISTggAAAMd42u2WSw7CMAxE3STNBzYcgLtwtkrcgItyBBakm4cGd4eEPJtRbHfi2I7S6/2xmV26vZEnL5PT7WmBQCAQCAQC37BECf6iHx//gfAnMONX2MvkCvvOHXHUNWGvIo46Hfm1ySesV3DBemDfJvz7fmfoNOHvIq6I+g2xpj7zrM65C+rZRF2z6HN26jhEfzhn6tyrmLcs9LKY1+rMOeeYdnIR9irmyOA/eq/UfU5Cz0T+JnST01dzvvfy9PaPd+Q3SEffkxdCOwUN +1438613814.950,1.000,0.001,HISTggAAAMp42u1WWwoCMRCb6bTb7oIf/nsXvZrgDbyoR/DDghAIU5+rMPkJZUMmnT62u9P5KLI9yA3WWTun/UUCgUAgEAgEAvf30dr19cW8ibCQsZHv+G7MxHcCfQE24lvBF3UZWIn/DLqZ5GqdF5JjAi6gx3ElvpX4NxgvTu6NUw/7U5z5NaKfQIf7A9cR+6REZ2Rfm8NpcD8zNie/OvkKOV9YRx/MkZzzqk4d1kd2f+Qn7xcdzD2K9Gf3rL7JXz5c51f/Z2uv79fmeQVRHAU/ +1438613815.950,1.000,0.002,HISTggAAAOl42u3VzQ3CMAyG4SQOtFAEC3BgE1gNiQ1YlA3g0ObySZYr/iTQ+1ysyk6cpiHsL9dzSqdDGtkU8xh29+MtAQAA/LP85Xmzk88vzlNm1lswjzfenH7VGW9ObOOWUr9w6szpZ/Lc5uskDk6+SN+W306xl3rtYzK+xbXUd06f6jxrXVvHRp5XEgfp18t8mtd9r8H3MuccWPA++h76HYtE77x6573MPN86fxecs+Kstwa/GwvWFY3PwfqjvNcn2o8c7HOamS9P3mPvvm8/da/jN/5/AQAAAAAAAAAAAAAAAAAAAABASvkBmZsGSQ== +1438613816.950,1.000,0.001,HISTggAAAM142u1WwRECIQxMQkA4PxZgL7ZyrThjBzZqCT6Ez87s4MPzHpf97ABhSYAEro/nXeSyygeps3a220sCgUAgEAgcG/rnebGf++xjIv/BAZvYG/4jO2dg7HfgBO1hVzqfQAfXL0QvE13UH/YN+itwATu0x3jPJI4Guhn8KGR+IzoLzK8k3kr2w4AXaFcSh5NzcaKrMK7kPB3WM3KPEhk34hfmF/PHJ3mhk/uFeeMkj4ys8y2zumE/qi+20XugG9VX3bmeyuS+HP4dfANrMwVM +1438613817.950,1.000,0.001,HISTggAAAM142u2U3Q3CMAyE7fy0UCHBAOzSlVgBiQ1YlBF4oHn5pFOKWsSL7+UU53xxEifXx/NudrnZB3lhXzjNLwsEAoFAIBAI/A++cb6n9w6nznyGX0Fegq7xCK5C3+ID1i2IZ8QrxkWsR3/mNd8DdG18XviI+kfhW6Gf4Mf5E3QT4gVxE/uu4rzUOQxiPzxH9sco7nsS/cF7ZR+YqCcLXRGs+rDX9yrfUK+LenrvZvjy3a2NJ1GP+gfW5nvHz3f699JO/+PWPP+RbwB98wbuEQVr +1438613818.950,1.000,0.001,HISTggAAAM142u1WQQ4CMQiEtnTX1Yt3/+LbTPyBH/UJHlwuk0yoh8ZomAsp0IGFZdnL/XETOa/yRt2l7rJcn5JIJBKJRCIxAp3s/+/10sl1L+SeEr9P7Q3s/l+5gN3Ajmf368BvJA7GPwV8C8geyDboj3FdrsTfgPcAej9vkIfrj4S/kXor+CvUsZP6bpBnJXWspP/IW0lc1BfCZ+S9ENJ3JX3BeTAStwV8QuxGnpvNK+Zlg3MYzbOQfguZHxm8r4PfMw3iz94z395ziR/f0y802QT/ +1438613819.950,1.000,0.001,HISTggAAANh42u2WzQ3CMAyF7TRpm54YgAHYgtmQ2IBFGYED5fJJTwmlXCq/y5Or55/Elpvz/XEzO13sjWFlXzldnxYIBAKBQOCY8LiCQ/VJ6VJDR/vzHszCTg0/vicHwVnomKesXGEz3gj/GXr6FeQZRf0jeIZfRd4FcWboaCd85zmqqKMIXRV+E/JlEZe8oM9ZzEcR8SfRT85RAg+iP6kxV+yfow7vnHM1x94ZJwm9C13rfhRbo27rPAfvVe0PtXe8cx/5xj3mf9qbtnM+//Kce/2HfWPffs77AqUGBSI= +1438613820.950,1.000,0.003,HISTggAAAPh42u3Wy23CQBQFUI9tbLASpYEUQQcpIhVFSgdpNB0kC2BzpadBiAUS52yemM+bj2ds3r9/vobh8zicTOfYTuHt7+N3AAB4ZO3J1906+5Dl4435p6J/ths75Wvku8T5HHfneCjG30VcIv+lfB+/h8izxbiHqN9H3q3Iu0b5Enlfi3m/RL815pH7sBT1W/TP/c91rtE/n+tS7O8c7dfiuYwRq3MzF+dgKvpV42d9npehOIdj0b512lfzq9ZZza917uPUmVfv3o5F++nKe9pufI+Md34P9Vw733t/R571u+P/AwAAAAAAAAAAAAAAAAAAAACPqP0DIDcGYw== +1438613821.950,1.000,0.000,HISTggAAAMh42u2WzQ3CMAyFEydpCQWxQHdhHdZAYgMWZQQOhMsnPSW59eB3eYpjO8/OT7u/3s8Qbo/wQ2ocG9v9ExwOh8PhcDiOgOgtGOpPnOwj40zYs4gz/Ef2+O+/IG8RbGJ92qm7ggv8K3RtmD8LPRvGl8ZX2CvyVNTN9U9g6l7FPOMX9IN27kcR+2Cir9Szijj6J7FfPabeReRLon6e39w5v6r+LPqk7k+vjiDG1rmHcfD+2+A7kCb9Z9+b2fdqNt+s7qN9f+IX5pUFYg== +1438613822.950,1.000,0.001,HISTggAAANR42u1VuQ3DMAwkKVqynSILZJfMFiAbZMmUGSFFrOaAAy3YQBpeQ1A68ZNwuj1fD5HrW34om9XN2v0jiUQikUgkEonj0MF1OYnPzhuJp4TX4eAX8A14DuudP4PfeQ34FfhTcK6CxfwTyY91YdwVeAvsLyT+DPU0sBfYX8HiHCvU0SAv9j8F8zUyB4d3UMj8sQ4n96fB/Tl5j4W8SyV9KOlLSXwn5534bA5K+BbU7cF9SLAf5WF6Ec1tr/7oTn3TQf3SQd07qoujOm3yX5z1b+gXMUUFzQ== +1438613823.950,1.000,0.001,HISTggAAANl42u1W2w3CMAy0nZimDYgF2IXZkNiA5RiDEfgg/Tl0SuCHh3w/VhzfxUrPbQ/ny0lkf5UHUovaoh1vEggEAoFAIPBN0D/pVwd5+qLOaP3Tfx/UGdTZIH/NZ8J3co4BD89JRN/IvkNc8wXWU4sb2F8g71BfIL/2M7e4a7GCzgz8LegsoOfAr+QeHHQr9Iu+mAg/w3mJ6BRSZ5DHtRB/ZPL8FPYz8asTnyrxH/OxEh8y3dH5kI5eInOBPO/MO/JSZ55YP733kL75Pu7V249+L/TDOqP3pncPIgXJ +1438613824.950,1.000,0.001,HISTggAAAN142u1XOQ7CMBDcw05MEAU9RX7C25D4AR/lCRTYzUgjGwogaKdZZa/xen3ldL1dRI6rPOFVapV2vksgEAgEAoHAO9CNx38bBnVopz4brH9Un/BdWGWG7wQSx9PkTOrCOAd9IbzIP0F889+BPcN4ZvDbA2+zL4RvIvkyyAR5FrAXoj8AT4F8DnHoJ6SeTOYL50fBbyL9S2QdYT/Zus7E30m/cR2wOB/My/qlZB8omQcjvNbZX0LyKvlPY3HWORek06dXz1Pv2G1wnL92/9iHeKTT/3+/5zb7vnkAl1IFIg== +1438613825.950,1.000,0.001,HISTggAAAM942u1Wyw7CMAxL2pU9QPAD/AvfhsQf8KMcOXIgu1iyvB44DMUXK63rNGqX9fp43s0ub/uiBntwub0skUgkEolE4p/gO8vvnfFWPyfvPxd6HG/4fgwegg8QK24irhAX0K3zC+Q/EF9cv+pOwaOoB+M5eAo+g24h/rjPWex/jY9EV4GbqH+Ac8R8E/EfiT+7X3huTNfgvlUxjvcP6y9kH2r9QL6DQhi/E5w3Ub8RvYn8Ltb39g0n9av+VDr9e/vNr/v41jze6bOX/6B/AKmjBdw= +1438613826.950,1.000,0.001,HISTggAAAM542u1U2w3CMAz0IzSFSogB2IXZkLpBF2QERuCD5OfQyVAV+PH9nOwk9iWxfZ6Xq8jpJk94Y21sl7skEolEIpFIJP4P/dAfxUG2YB33CdjIDnYBf+dK9lVgh/UhiDs2nkieAfzdPsB5zDuS88g74B53D3FY/kruqeA/El0TeS/UhfGU5Cvw707+HfOwOrWgPgqpvwJsxHZSr4XoMrJfSFwnfWNw/0gv6z8huhXyR/3tb/Z7NB+2mku6cv7Zl+bnVvrkR/oS697vpc4fR/sFvw== +1438613827.950,1.000,0.000,HISTggAAAMd42u2WwQ3CMAxF7SR1ChcWYBdWYRUkNmBRRuBAuTzpKy0qnPwvlpOfb1t13Jzvj5vZ6Wpv1MX6YsvlaYlEIpFIJBJ7wLPOn+q72F/Ld7wHC/gNvI8/Cb6L9yX1qRNY7+CTp/Rn+AfoBHgB64gf8CvyDOgzzoTzXdRDe0SeE3zWGUJ3FutdnFf1qf2139kH/aX6Tem3ga/yVfekgK/qGPV5EferifpNxFP3ugpdE+e3zok64I/myLfzcOv82msul3//J16O0AUs +1438613828.950,1.000,0.001,HISTggAAANJ42u1W2w3CMAy0Yzck7Q8DsAuzIbEBizICHyQ/J51SoLwk34/lxj7nUsft4Xw5ieyL3GHNarPpeJVAIBAIBAKBX4Q++Fw2iv+2Xh+sJ6KL6TSSzyzCgWcCPuR18Htebpb9l3beHanT/QV8A94M692fgb+AL2T/WBfPoRJ9FeIK2IXonSHfIS4Tv0K+A+9EdGfQi3oMrJNzwvevJN4G/ZZIPOtr1t9G+kDJPozs31beGx3YNLivunJe6aCuPFnn1XmrG8/Bd/H+y/fgU7r0BnEZBQM= +1438613829.950,1.000,0.001,HISTggAAAM942u1WuQ3DMAwkqc9PkwGyS2YLkA2yaEqXLiI1BxyoIkBc8BqC4h0fS5Z9f72fIrdDvkjdarf2+EggEAgEAoFA4H/QSZ4RPvr4v6dOHsZn68NmsJh3+AX8DXQVeFh/xBfQFfAX0KG+dbsS/YjvoN9JnQbzFIhvUG8FnQK/kryVzNXIPmQyZyXPuzr7kaFuIXOYc87wnCTiJ8I30gfGs+MXpz+d7CuROZT0ZeQ9wjrirCenjnevmHM/yGR8luf1pRe9j/Vi/cR38Ed1Tg+0Bfs= +1438613830.950,1.000,0.001,HISTggAAAMl42u1WOQ4CMQy0nd0cuw30/GXfhsQP+ChPoGDTjDRyAFEAnmbk2J44l5XT5XoWOR7kgbSz7mzbTQKBQCAQCAT+Efqldakzzlgc24An8n9MJD4RvT5eQbeBPZN5J6LX7QL5nRfiz8ReQD8DV5K3gn8Ff4NxXF+DfNz/CvGzs24h+1qccyxkHqZjZD8SsQ3q8+5Pdu6jkXueyftIhDPos3fD8pXUI4N68uS7VTLvaL9Qcg6j8a/2Kx3sX5/uz/pmvEngJ/8Dd2E+BOw= +1438613831.950,1.000,0.001,HISTggAAAMl42u1WuQ3DMAwkKclSkCYDZJe0WctANsiiHsFF7OaAw0VtwmsOJnk0H0HQ/fVezW5P+6Ac7AfHY7NEIpFIJBK/Bc8R/OWeXfCJSvwN/AHvR+RKvhewB3ADPuM71FMgrpM8FfwD7FeIHxCPOuSF6LD/QeY7oA7Mg3NAxnwd/CH2UUl8F30xXRH7dXFunOwRdReia6Rvdu4LnP8m+hlE76T+EHMIoffJ+lUd6v4PkpfFubh/1H1jk3r78r/KruZmk/58R5C4HYBqBUE= +1438613832.950,1.000,0.001,HISTggAAAM142u2WSw7CMAxE43yalg0X4C6cDYkbcEmWHIEF6eahkSskRCU8m5ETe+x86vR0vV1SOt7TC2WwDc7nRwoEAoFAIPDfsKhvl8gf7gP93v7/YJuYL+As6psG98ENcTPshDiVh3qT0Ft5Ac/Qm4XuAeNrngq/RcxXod8xTj3mr4IzbFVvgx/3l3l5zh3xxbkfPA/6FXHfmqNfHB3WV0WeLOY9LoKbc1+zsw7WY8JW35m3LhWv+kDauA4Tceb0n1/1bduJrn053+b34Alf6gXa +1438613833.950,1.000,0.001,HISTggAAANF42u2VSw7CMAxEEydNWiohsecA3IKzIXEDLsoRWNBunjRyhdoFkmczij9TOx/3+nw9Urrc0hdl4byw3d8pEAgEAoFAIHAc8kE6nq5hXZFHNsQVMP0GzsJewQPWDfYB+VwX5K06Hf4OHhc+IW4Sdaz2M/wj7Kxr9c/4/iz2oYEnUXeDjiFO1eGdaxXn2sX5c3893eLckyzizImrG/tT972I96T0m6PDOtQ7VP3TX504c+ZEEX0m0cev88oOmn95Z9288/yN/9d/17c1L38A5GkFGQ== +1438613834.950,1.000,0.001,HISTggAAAMt42u1Wyw3CMAy1nTQEuDAAuzBbJTZgQUboCD3QcnjS0wNVQoD8Lpb/tuI4OV9vo9npbg+UhfpC4zJZIpFIJBKJROLz8I1+z/8c0QehBv/CQuoJkA8kTpC6WPwOfhX41b6BntGB2DeIdwR9B/898KvdAewxLsobkav6d+DXSbwK+bF/xhfSX4fzwfis7gZ1q7kIkcdfnKNK+gpip+ZTzX0l/uxcQ9xTdT9D2BupK0R+F3vHxR7xN/eZC/nWPfgt/r/6jvz9+zoD8tsFtg== +1438613835.950,1.000,0.001,HISTggAAAMx42u1UyQ3CQAz0sSQbxIMC6IXaItEBTVAeJfBg9zPSyEkQkIfnM9r1NbYsX273WeT8kDe8sTa261MSiUQikUgkEvuDLvQz4KX++HZgzKtgL41H8Cukj84HUk8gvv9P8O7xFeqOhCeit8K75z2Cf/cbiJ4CcZgX45AHwmh3Mj+sg/YT2NkcKpm/Qd4CzPZEoa4FccgD2Q8L/o304YFOps/JPnugX1faNdCnRId9eD+iuyAr78vW+8b639rPv++y/qjuXubytXm/AI/9BZQ= +1438613836.950,1.000,0.001,HISTggAAAM542u1WyQ0CMQy0c2yyggcF0Au1IdEBjVICD5LPoJG9Wh6APJ9RvPas4zjH+Xa/ipy6vJAH6+B0eUggEAgEAoHfhEYJ/nI9GYsx9vpPJGCMf3s3Di7AathnXCX+C/wvg53FTd0O/qg7/RroHwcfIG4FbpBHB/tC7MhMn+W5Ev1O7IXUr5H1LTCv4uRqxGXSXwvpx2TkV0h/VDKuoOvdX83Z/8mYpxjf1bn/LTvTTRvPm7zzXtl6PiXn/Lznl3woX91YB91Zt295N+gT5+sE9A== +1438613837.950,1.000,0.001,HISTggAAAMN42u1WQQ7CMAxrk60UhsQH+MvehsQP+ChHjhxoORhZruAAQvElWuY67pSlPZ4vp5QOt/SAt5hbtPWaAoFAIBAIBAK/i0yik/vd8543qKP4BnECXq8/Ay+TfF9fWtwN6mxarIS3B93O3wK/5xfisxJ/C9FHXxXWFZEvZP+zqId8Bz5+byc8pmtE34hvE32JflTfuegLtl8T/wX2r4nnTN6P7stF/0+krpoHys+7cyUN5v3DeWZE/1vz9F/PjZf8HdYNBeY= +1438613838.950,1.000,0.001,HISTggAAANN42u2W3Q3CMAyEEztpy48QA7ALsyGxAQswIiPwQPrySafSUvUF38vJiZ1z0+iSy/1xS+n8TB9449zYrq8UCAQCgUAgEFiOvHLer30oHcN7kOPGd+JEHfMK5se4E+/QQ+MB7KJ+jCt4j7we89Q/YnwH7pHPPk9Cv2Cdijr2aUKP9RxX+1qwnol59uvQM/xHF30W8CDOEdnFuclCvyLPROwiVvpFsDrn/uX3JLFeJ/aniPqp/UsT/eaZ/qD8Yq7f2EK/W9tHbSO/3drn/+ZefQM2tgWd +1438613839.950,1.000,0.001,HISTggAAAM142u1WSQ7CMAy047RZ4MAD+Eu/wJeQ+AEf5QkcSC4jjVxOUMlzGTmNx0vtqtfH8y5yuckHNlgHp+0lgUAgEAgEAoHjwMBWwhNp5/1E2Ej8+TzDeSb++B+agRfg7OTTSB7o3+C8DD4NXoGxjgp+jeRbiT11O+gUEr9DHp3UV52+nYluhvqxTyvkZ0Q/k3nA/hXyHhV0V2e+2Lyy+Upk3hKJs8B9c+J7c57I/n67b0psc/ZenX4Y+U4wPS8fcfRkp9/RoD+u61/6qG9GnAVU +1438613840.950,1.000,0.001,HISTggAAAMd42u1WQQoDIQzUGI0LvfTev/Rthf1BP9Kn7RP2UL0MDGFLYWnJXIboJA4axdv6fKR0faU3yuA8WO5bCgQCgUAgEAicjwzsoXxpHQHOUB/XQb2S+ZlnJFaIFydfyX92xm1wHdyBDXQG+ln/AuOF5KGvDjpk5mchdQz8NOLLyHng+XXiu5L9EYiVnH8FZvMZ6rF+EtKnrA+ZjtVtxJ8694/dG9aPnk4cHwh18o++B5jv6bx9Ofr+fJp/1rv8r+sFfuR/sAO9OQWV +1438613841.950,1.000,0.001,HISTggAAAMt42u1XyQ0CMQzMOCQKpyiAAuiC2pDogEYpgQfJZ6SRQQEJgeczyvqIY3ud3cPlek5pf0wP5M7obKdbCgQCgUAg8F1AxPWXdcZk3vEmfYjvRpYb6fF68IK4dK7CXsmVHstXtK70fNhtOy+JN2L/EX8jvSzOtyb9SvLm8Ih35+Qjk98i8p7JfxH5ZjuIfZrTJ0nUCSKuIvrLyJ+p/xrBqj6qX/Fkn5s4jxd3cvThsPKn8m/Oe48X5w0m5w8m5+Gn76tZu1+7J3EHghEFDQ== +1438613842.950,1.000,0.001,HISTggAAANR42u1VSQ7CMAz0krYp9MAD+Eu/xRWJH/BRnsCB9DLSyOkFROW5jMZJHMdxnOvjeRe53OQDb6yNbX1JIpFIJBKJxB7ol/3qQfMSnRfZOuczOGgDu5P9Rhg3YCV2J1waD8AzjI8wvulK4ivkfBPwTOIs4B/9YlwTxLfA+gr7LWTdps+gT6AL2J3kqYK2IP/sPIVoJ3VlMM/IfRhZp2AfwF4CP1Ed4v07qW+F/Z28Lyfvz0l9Sud8JVqC96edfcSDPmJBn9Kd/U4P1ud//U/+fV7e1gcFeg== +1438613843.950,1.000,0.001,HISTggAAAMx42u1Xyw3CMAy146SUcGEAdmG2SmzAoozAgeTypCeHVOWA/C5P/jt1mrS3x3MTub7kA2usjdO9WwKBQCAQCAQCR0AdeVa/157g+zABC9Ezf2QFLmDPJJ+BP8on4AL5MG+Xz41XiK/gX8G+gh7jjcRXqMv67+tegC9kfZXUzyR/hjyFxBnUNzKfQuwLmaM3fwVZiL44fRnZL0r2JZOFxCv7nxrsR53nol+yDOrZe8/WPXu+6OQ56PU52sds/0ed87+u/+/35u58b5/dBdI= +1438613844.950,1.000,0.000,HISTggAAAL542u1WwQ3CMAy0HVOCQIIB2KXrsAYSG7AoI/Ag4XHSyWkB8fF9TnLOZzl1rZxv96vI8SIvlMba2OaHJBKJRCKRSCT+j/f77MP86FwXxo0wviudcNdvgAvwtvEE8dG8QvK6XwW9g95JnkPeCfy6bt94F9R30u9E+u33d4B4JXWwPyV+lejQV8l3xPtm82GBTglbMIeFzIsHutE6SuY7+g9YXAL/6F6iPaEr94gt9F+7fyL9t/1/vaeH9U8gaQUu +1438613845.950,1.000,0.001,HISTggAAAN942u2XzQ3CMAxG4/y0UMQG3BmD2ZDYgEUZgUt6sfTJKqiiRe9dPrmxncRyk/byeN5TuqZO6Wpd8+2VAAAAALaErey/NfLCfZqIsyC/ie/B4jSLuJkq/FR8czq4PD5ufn5w48pf5R1cnmPXUdRh6npy8bN9dnYTfqObt7n5J2dnsd4q8gxOW1CnaLyKddRgvhb0URH9k9V/iejrKrQE/VhFfl+XFPRzEnYUV8R7a8E6LaiLeu/VflXepefo2ufyp+e4fen36/tjr/fX3u9dAAAAAAAAAAAAAPhP7A2XSQUI +1438613846.950,1.000,0.001,HISTggAAANZ42u1WSw4CMQiFdrCdzMYDeBB3ns3EG3hRj+DC6eaZF8hk4ifhbUgpPAgB2tPtfhU5nuWFukpdZbk8JJFIJBKJROIb0D/n3xpHd7pXYu/pi2NXQKL+QPzGP3MKxjGQE/AMdPAfdg3yaeDf4czidMh3JnzjvBDeBvwL8BuRDeIb+HViPxN9Bb7u1APzNsKjpH4Gdnhfib04enX6rJC+xD7Evq+O3ps3rIc5czY581Sd+a/BPEpwP0jwXki+UTt1/HTj/ozuTd1pv/7qO/Sp9/Otrk8wowUA +1438613847.950,1.000,0.001,HISTggAAAMp42u1UuQ3DMAwk9dpuskB2yWwBvEEWTenSRaTmgANlJ0YaXkNQIo8nSuJ9fT1Fbpt8EJvVZsPjLQ6Hw+FwOByO66CD63oyfrROhPVE6nabSX63odkCfiLxGeIwH3kj4Z9gvYJO3J9BV/cXyJ9BJ/JkwlfJeRLUwT4sUCdD3kT0FdI3tIn0DXkZTz14n4W8F+YruXfLKvj4ThPRrcb7svSo0e9o/Kdg7Fv/Xww+If88GvGj8yd8Off04Hz811z+lb6r+U+feweubQXe +1438613848.950,1.000,0.001,HISTggAAAM542u1Wyw3DIAy1wQm0HLJAd+lslbpBl+h4GSGHcnrSk6GpevK7PNlg418It+frIbK95YPcWTun+y6BQCAQCAQCgXnon/wkR2Z+dPJcxXciML4jDeQF2MDOYL2AvpLzDLiAn0r2F+DqyBg/5s/yQ/218wX2tc5b5xU4QzwN/DYnzgX8YR1WUnfWx0r05tizOcpkXZx1Nv9K5iwRFuKXxYl2Xt8V7GUwH3Hq4n3HRvLRL++J5PRlNK588r5Mg/Z68hyZ7Ic48/7r/4Qeq9IFhA== +1438613849.950,1.000,0.001,HISTggAAAM942u2Xyw0CMQxE43iT/R1ogF6oDYkOaIayKIEDKyE9aZQsAsHBcxn5G8fZWNnj5XpO6XBLT/jGtnE+3VMgEAgEAoFA4PU++lfkzrpd2E34U8/3YgFTnwUPG0/QV9hHyAV1MK7A3yBX5M0iboT/DPuMuBV1LYinPGGdCfkWrLPCvyBuwPpF9E/tu3b2oYq+Do3zcOjV+Rn0hjpc2L3xvWXh5+o/SOzLBadOOb9537Korzbup+2cX/6h+Wed82nv/EqNfv16ftuX8zXlB3U9BbE= +1438613850.950,1.000,0.000,HISTggAAAM142u2WQQ4CIQxFS2EYx41x71082yTewI3H9AguHDYv+eIYNcb0bxoK/P/bAOFwOs9mu4vdkZeYlujHqwUCgUAgEAg8QvpzvW/7ULz+om4R+fbvG8CbMG/in5hFdJE3sb90+Dk/Qqf5r1jPOAlfTW8LfupXzFNXjZvupuNzgG4V9Q/wy77soVcwnkRd9M2+Ma/OiyMW+Evgc/hzUbfqQxZ+1DlK4j7Rvz15Pqk3Cj0T673D19u3Np9XrreOv/zh96/Xj19/19/OdwOAtwWA +1438613851.950,1.000,0.001,HISTggAAAMt42u1WSQ7CMAz0kji0XHgAf+FrIPEDPsoTONBeRhqZcgCEPJdR3LFlu3aU4/V2ETmc5QlfWBe2010KhUKhUCgUCr8Hg7MCC/nO/I3EQQ5iR/8GvL4zO+gD7HuwO8QJOAt7xyZ+QfKbE/2a3wQ8QD+Ad6S+meiD9LEn+g76ifhNpD4HDsif5eNJPCP/v5P5ccKN6BqZS0/m1GGOOpkLjD+SPTCSf1anJfub7Z2+uM+N9EcSe3aWN+8l/dI9qhvtW/vwqTj/Bn0Ab1gFhA== +1438613852.950,1.000,0.001,HISTggAAAMl42u1UwQ0CMQxL06ZXQEIMwC6swipIbMCijMCD9mPJSgFx8Ig/1jW1c23SHK+3i8jhLE/kzqmznu4SCAQCgUAgEJhHWlnHfNLk+qv5s+OvwLVzgf0KfgXWG+jR10jeDfghG3CD+NAvwJXw0G+J3wK+I+8evtk+A78G5x26Heib41PgHEbuHetdSX2V1KOQehvRsTjqlfS1TfaV559JHPOw/kd/9t4y0YujV/KuxHmf784hT68/nnv/Puc/nbtp5f/9+v09AAFoBTk= +1438613853.950,1.000,0.001,HISTggAAANB42u1WwQnDMAy0ZNlJQyALdJfOVugGXSLjZYQ+Gn8ODqUlhhZ0HyFOsiTnouT6eN5TWtb0Rt6t7FZvWwoEAoFAIPDfkJj3p+5FDvLSuZ5XJ4NViK/kPCX5Ld7If2fjR/ArxBdSr4Ct4BvEG+GbP4A/k/4K9DcBPwHf8i8QN0NdPH+BuOrMjfVwzgHiDPrC/EzuT52+M3neeB7TDdMh483RHerY06M4ustEn6zOSHh17st7z4Tw8uH+0ZP36Vn7Tg7m9d6b3+7pXt8beQFsTQW4 +1438613854.950,1.000,0.001,HISTggAAAMV42u1WyQ3CQAz0sfEm4UED9EJtSHRAg5RACTwIn5FG3gchH8/Hcjwe29lDe7k/biLnp3zgm9XN2vUlhUKhUCgUCoX9oDvl66A+xo3wbNB39q4E/jfegDdBnOmiXUndGeoE6C6kn4D8hdhO+B3sCeZr4AeZt4HOCnUC+J34HfQa6T8S/kzWa4L+Db4r8VHfyX7B/exkflYH/6sM+pjfyPoY6UOTfS+JDjtn2fn2JM+Tvtn8mvCOuj+Pqv+ve/7n/bwB/0UFqw== +1438613855.950,1.000,0.001,HISTggAAANV42u1Xyw3CMAy1naQJFQcGYBeurIXEBl20I3AguTzpyRUIioTfxbLjv5M0Pd+Xm8jpKk+kTrVTu6wSCAQCgUDgt6F/FjfmvY339MyZozr+xrsxAy9EPuwnoJXoNZBXsEP/o54C8gTyidRfIW4BvUTWBz10Ond6hPVG8qtgV0idDXjMF/uJfmawN6f/LE+vn5XMJ+N/BpmDEf3s5JlJfbZx/yPFvAvxa2T/m5NHcvJIxL+R88riCeGV9F3JOZYP3TPv3nv6ov1e3zvdOf633wH6AAwKBSk= +1438613856.950,1.000,0.001,HISTggAAAM542u1XwQ3CMAy0XadpKyQWYBdmQ2IDFmIkRuBB8jl0chFQIeH7WL7YPtdNovZwvpxE9ld5YGhWm7XjTRKJRCKRSCS2hP55fd0or8c74Q38yArk9e/KAjxaXHeoW4AfgR/AOuExvutNsD4R3bnZCnyP34Ffod/uL6SOA4/9LMDPUN+J7kjmg/NF3UKes0I+07FA18l7d1LHyb5zoqcr94UG+wTrC/tvCs6Nvdi3kf4kOG9C+rUgX1fG65v3kQa6v3q/f6tP/dBcn+LuoloFng== +1438613857.950,1.000,0.001,HISTggAAAM142u2VyQ0CMQxFYyezhEU0QC/UhkQHNEYplMCB5PKlp2EOaJDwv3x5t2fi5Hy7X1M6PdIbubE19sszBQKBQCAQCPwzbKW8NfLCHMSUR+0u3P2KyJOw+g0iK5v46VxF8hfQ97i58aFxhX72jUdh6nOW+M47kav0NYpcJV/XHyHOIU+B/ifJr/GD6HWOAv/ZIU7tBnkczp3D+V1bz8SeYX7yoz6p7wzfk/aswD45+PtCvMEeJdgrW1nPF+6F/OH98qv359bvydfqvgCePwXN +1438613858.950,1.000,0.001,HISTggAAANB42u2XMQ7CMAxFYydpAmJgZOAuPRsSN+CiHIGBsjzJMokYQPgvVmLn+8dy3fZ8vV1SOp7SE3mzslld7ykQCAQCgcBvQkL/V+uRQX5+p4nDK86+GnF5UD/5FDwL1mVwTZ6KvA1+xtU3eWkL8nTcryL+5d/hvMXfwNOhq8EeNrvHPvN1+NXQWY16NejoRr3Iz74oTj8sTv+w/tmwVt7s9KU6+vlcWOezs59QXzXupdb/mKMvO/PAu7c6c2Z2Xo3Op9k5Kx+ew//yfp2u1wMkxwT6 +1438613859.950,1.000,0.000,HISTggAAAM142u1VOQ4CMQz0ld3AFnyAv/C2lSjo+ShPoCDbjDQyLKJA8jRWFGfGsR3nfL2vIqebvODD6rB2eUihUCgUCoXCJ9BKwa586I912fpda4meg9+2bmCd8PdhJ/Df+ILwzcDbYR95URf1gvA46KH/AvFOYI8k7gOsZ/Dv4Ndgf0l4GqkH5kvIvZzkOeB+QepkhC9gX5P+YLysnpb0MfazJeeUxMnypeTdGYkX35Ek54ToCbmvJnPGEh1N5own84Xpyc459S//mH7Jr08lJAVs +1438613860.950,1.000,0.001,HISTggAAANZ42u1W2wnDMAyU/Hag0AG6S1fJKoVu0EU7Qj9q+nFwCIf0BbqfI3o48kmxc7reLiLHVZ6Ig3VwON/F4XA4HA6HYwb64+v9u646qdfrv47Y42Q+5gXix7g0OIM9kzoq5BWIK2T9BPUUsAvkIffBbfBC3lNh/QrxDfwddDmQuivhZsQvYG+gbyW6d7KPQvqbjb53Q98E8ew5kLkVo/84v5n0Xcl8IQKpi81/JP5E5pDpGMj+tn6HYvjVOC+s88E6r7fGfete0J3y9EN16Jt12v1efwAa9gVH +1438613861.950,1.000,0.001,HISTggAAANJ42u1WSQ7CMAyMx2loe+IB/IW3IfEDLjyTJ3AgXEYauaBSEPJcRrKdsV1n6eF8OZWyv5YHvLN1xvFWEolEIpFIJP4R9mN5bKH9XT0EehB2jneKd5HHiQfxv9k674hB/lHogPwQ9tp5Ih1eV8k/UF1P/yzsk6izEc+BX8Vx3Y3yV6qL49VcuB+e0yj2BeeHiKtibibWu5gn77dIl+eq6kWwv5W+C11f+B24DwvOoQV9qvpc6Hpw3hHktY3uW1tZ71Vgpfu7fLmPrd6fj72TdwTGBYQ= +1438613862.950,1.000,0.000,HISTggAAAMt42u2WwQ3CMAxF49S0oQixALt0HdZAYgMWZQQOpBye9GU4Var8L5ad2v52HSvXx/NeyuVWPhi6tC7r8iqJRCKRSCT2BcsW7Lqv33dcwK8K/xrUE8U5wF7xznTYHX4Ndur8nvY1zwS9QWedE+LyXdxwfhb5Vzkirgs+LviOkIxLnscuZ9GnWfi5qJ/5T+L/KT5V6C7mg+cWzA3n0YWu5jnyY151P1S9JegD567+eK/Jbwjum4KLfWFCqnPF0zbei7bRfv43j70BGWAFNQ== +1438613863.950,1.000,0.001,HISTggAAANZ42u2Yyw3CQAxE1473EzpAohdaA4kOaJQSOBAuI428QgJymHexnHXG3p+l5HS7X0s5XsqLZbO2WT8/ihBCCCHENzDpfqSPcQ7+MqlriR9k/J2vkfwB1qGuCj7qBcStRM9AB+MG+Cs8b5DHwD9AvgrvddDF8UbmHcQOiB9kXp3kqfBeh/pxPRrRjcl9wn1tJB7Xl+lX9j1CzmEl59RJfUHGg5xfT+pCXUvOp5E4JzqW3F827yzvrL4l99+SvvPr/jnb1/bW9/+dTwghhBBC7OT/yBM/JgVq +1438613864.950,1.000,0.001,HISTggAAAM142u2WwQ3CQAwEfTk7JMqHAuiF2pDogEYpgQcJj5VGhggkHt7Pyjl7vdHdOTldbxez42RP9JXbysP5boVCoVAoFAqF36FB/Pof26k7gE5L+ioc1nviOyRvFB/EAfUuOhp34YB44xnqD8KT+FO9UfJn8Reyrv22/AX6L5JHeiF9XeoD8jvskyfPA/L0nDnss0P/DvUGfeg9HM5jAx8O59iByWcW9+R+mvgdEt+W3Gu63zQPsvpsPrUP473zsn1J7925/O/fjV/ptQfTwATx +1438613865.950,1.000,0.001,HISTggAAAM142u1WyQ0CMQz0lbALPCiAXqgNiQ5ogBIpgQfhM9LIC68FPJ+RnXhsZ9dRjpfrWeRwkyd8sA62010KhUKhUCh8F7T6+au+9U2/4XsPbPQ7WRdYlyTuZcfgNngC/wY4YN8Meg1sI7r7wTvwd5LXSR0d9LeJ7kz6deJvJJ7l78CNnLcT/QZ9GIlHnYl8Twd9JYz1RLJPk34RkfzPBtwX1snOF+NYfpwbrCeSeQwyd0HmU5P5zfbLQnvpPfWpbnaf2cruZ11JHb+aXx9xCwWq +1438613866.950,1.000,0.001,HISTggAAANN42u1WQQ4CIQxsS5FFLz7Av/il/YKJP/CjPsGDcJlkQkwwkti5NLDDdOg2wOX+uImcd3kjtagt2vUpgUAgEAgE/hMavn6yL53Mkw/5/T1oLWbgGfAd348wb6B7IHkc8jnESt6rXa/AOAMf8zsZo5+N+OjxCONCfBXwVUC/rz+RvBl0DfgVvm+wjkXUr6AvpC5O+gbrb6QPHPRZHxnxy/otkT5Jg30zf0b61ch/SQOfSuorpN6YXwZ1GuVNk84NnXTerXa+65f5q90nq9zD+gL3jwVp +1438613867.950,1.000,0.000,HISTggAAAM942u2WSw7CMAxEE8dt2oLEBbhLz4bEDdhwTI7AgrJ5aJSWIlaezWic+NPEtXK+3i4pne7phbJwXtjmRwoEAoFAIBAI7Ef+U5wsdIsNftQOexb7Xdj5zuwWHrHu0D38BviruirWR2j6006uqIc8Ic4E/4L6K9YHsEMfhd87/wHaRZ0m8vbiPAt0he6xrxP3YKJP1vZradhd5LFGfYxTxH+h+jiJPjKRrxXPV353apxbasRRsI3x7cv5lHfOtbX77Mfzd++czhvv88P+BEhiBXM= +1438613868.950,1.000,0.001,HISTggAAAMx42u1WQQ4CMQiEdqH1YnyAf/FtJv7Aj/oED241mWRS1myMB+ZC2JZh2gXS8+1+FTl1eaGuVldbLg9JJBKJRCKRSHzeR9/uj/rvdxj5Ho3XoG4NnnPoWYjOCuuov4I9wP5hHSzyG8mH/C3I5yQ/3t8CvGhRX4dzNvCN+A3ih38EfTaxHuQ1og/rwGG9k/tj/92I76S+sB6N1F0h+Qupr0ryFlIfLD/rU4wvE55ZP7E+lI19zuKjPLpxHkbn2F7z9td8/6pHd86jTxkXBPk= +1438613869.950,1.000,0.001,HISTggAAANB42u1VSQ4CMQxrkgnDoArxAP7ClW8h8QM+yhM40F4sWSkwIA7xxWqSOu50meP1dinlcC5PWGNprKd7SSQSiUQikVgTkut/Ky/ARuoliEdjBX0L8kp0HOo2MJ4az5BX4BnqMe6QR+66Cxmj/hZ0sb7Hd40r1FXQRX0nfjrvg/kL8VuJbye+2bqc+DKyP8genB/so2R/J9LXyf6O9sfzwc6/kDj2YXl2P43cbw2+hwb3XIjeaLwEvj597+TFd3BUT770Dq89X37k6+/+dw9U8gVi +1438613870.950,1.000,0.000,HISTggAAANh42u1WyRHCMAzU4SQOx9AAvdAObTBDBzRKCTxwPsvsyJD80H524lV0xLKc8/1xEzld5Q1vrI3t8pREIpFIJBKJf4Su1Hvtf/WjnX4ceEEh/j7+B4FH0AvoGG8gfgR0J/lgnAHywPXleWpcQR9I3Ery3oG+xN0H+Ywk3gR8bDyDXSV5CdEniI/7UqCeAnGd1HmAfZjBr4K9EfZgPeo7J32K+gh1SNBPRtbZOSmk75x8bwnq10724ByxOWHEjwfvG5kr1jl37Mt5qMF822oOb30PrLXTF+VdBWI= +1438613871.950,1.000,0.001,HISTggAAAMl42u1WyQ0CMQyMj+wBElAAvVAbEh3QKCXwIPmMNHJW4np4PpYde8a761h7vt2vpRwf5QVrVprVSz9JJBKJRCKRSPwCEvij+cx2KP4HBjwdFeIW+KiH50uz3uwEeV1vhjjWVdBxyFsJ7w5095C/Al/3D0R/Bj4WP8G5Qz+oh/UL5DMefG9O+DT4DmwunMzFROaLxS2wQuasQn8W8OM84nNrUGdk3iTwldwzHbyvPng/ZeMe2bpvRveXfnlPfqpe/qzvd+nLEwVDBdQ= +1438613872.950,1.000,0.001,HISTggAAAM542u2XMQ7CMAxF7aRJCiyMDNylZ0PiBlyUIzBQloe+XDEgIfyXr7j2j+s4SXu+3i5mx5M9UVf2lctyt0QikUgkEv8BzxL85Hpx3UrAJr77KvwmEecinnYTOgN28oB/g31gvnnlDvskdKhXMd4jfgbvoPPyP4g8p2D+Bv2BcRN16mClV4ROQzzr1YN1Mvixz2oQ56Ifo/59+18Rz9nHNZinB/tF5c26cT+wL5uIU/mbeF8PzgPfeH4olI3njW/Mr3yYR+SX99aX750HPZoE9Q== +1438613873.950,1.000,0.000,HISTggAAANl42u2XzQ3CMAyF89eEgAQLsEvXYB0kNmBRRuBAw+GTnpxKiJPfxUpsP9uJY7XXx/MewuUWPsibjJtM6ys4HA6Hw+FwzCD6Efz0fKLw+36n7fQLBp9aJyEV/9Avwq/AP0M/1hU8FfrBc4Dd2G+Io/QN+iN4K/xOsDsjrw7/BB7KBvsu6usiT66ZR8M+6+K9VFE3z4/3WMEfRb8W0Ufsp0X8n2TR/1Hct3ovBXxZ9Ktlr/qX/lacLN6RNQeyqHN2biTj3as41txKO+3/PVfDzjlpIc3GfwNvxQVJ +1438613874.950,1.000,0.000,HISTggAAAMV42u1WOQ4CMQz0xImzqfgAf+FtSPyAhmfyBAqWZqRRIoFWFJ5mlMNjO+s4e77dr2anh73hO2PncnlaIpFIJBKJROL3wEF2heww0S2TcdB/44crjZuYL6RThb8qmO06+WO/jfZtwr4Lu02w07hT/Ox3UPwcT6P1mOTL5z6Eboh8YpLvWPx+QfPMXA9Odefi3EzoQOiG0IPQcVH/XOcQdhDrEFwW65zPb3afTexX9x2L/QVf6vlkHxb7Gw7ur//+LuAFr6AFgQ== +1438613875.950,1.000,0.002,HISTggAAAOZ42u3WwQ2CQBCF4ZldQNDYgaWYWJuJHdiQJVmCB8HDS16Akxr+7zLZdXdmVlA43e7XiPMj3uoYc4zl8gwAAIBflBs/5+d9zazTcZFYF9ZrZNyafGHeJxuJRcat7JvmO5OnNeeupo/G7N9JnOrtTb+DRM3Xyfy07ih5DvJ5NXHqq5e+BtN3L/ODOYeeu5X9Reb1+6rmunUyLmac5j5IyZvmOoZZl6b/kP6W1m1m7ucweYrpo5p1Yeq5/Dnze8+ZOnP71q6rK//Pyp88J7byfAEAAAAAAAAAAAAAAAAAAACAb8sXQekF0Q== +1438613876.950,1.000,0.000,HISTggAAAM142u1U2w0CQQhk2Qenl2gDFmAX1mZiBzZqCX54/IyZcHvfzA9ZmAH2xe31fopc7/JD3WzZrD4+kkgkEolEIpH4RwnWe3URr0zyShBXYjEuJN5hXYke4+4fQR9K8nrdBtb9Bn7XL8AbYHE/A3Se7wJ1DOqcQO/rlfRjpK+V9L0AD8/HyH5Yvw3yGfE3qI/32uHezuCv5DwxTyW6RnS68/0LeRfsvTXwd8hjhMf4jBdBJ/95NF90cq7IZJ6oH52cn3pwPh7d19F5Xb6FMAUQ +1438613877.950,1.000,0.001,HISTggAAAM142u2WwQ3CMAxFbadJSkGwALswG1I3YBFGYwQOJJcvfblBhZP/xbLjPNutEuW6Pu4il6d8lJrVZu32klAoFAqFQqHQ/6TE14370yDXSJ2t65gn4Hc7ER/fnxPYHq8O59DsQvgnwjdSv8czcDr/CPmZ+L2vAvEF4meHizz0Z+izwnqFuRLpC797gb4K4WD9DPkKcYP1RLjpy/9mZB7GVZJnxOJ5YvWF7DPnPJrDY/XY+TfnXvD69e6T0XttlKODc+1dR3aa91f79Q1wUwWW +1438613878.950,1.000,0.001,HISTggAAANZ42u2WwQ3CMAxF47hpUnFhAHZhLa5IbMCijMCB9vKkrxQBUpD8L1918h07cdycbvdrSsdLesFXtpXz+ZECgUAgEAiMDYst+AvkL5+nCVZ+8k698pPB27txhr2sXIXexfgEfy7sDXbOq/gmF/ip4IZ5Dt7yWKBbEOdmP4i41boNurkTb0VcFboJ+zyLceoL8lL7qc7LRT0UUY+snyLiZn3yXFjHvfVUXqruqVP3XN1333nfs9C5iM86edqb/abXF/Jg/dU+7Ke/+r/mQfdr1HeIPQG+YgVt +1438613879.950,1.000,0.001,HISTggAAANp42u1XQQ4CIQxsCwvrxoMP8C++bRN/4Md8ik/wIHiYZAKroonpXCbQUlrYtuzxfFlFDld5IBTWwna6icPhcDgcDsc/Q4FH2X9V//kuI/rWGGtjfqvfRuSR7KdEHuH9ORVOIGdsoJ/BfoB51KvyfeEd0Wd26v4z+FXjWGDMOAMv5DwSzEeyPkM8kcTD7M/kHtg4kXsJwLhOiZ401mHcRr67ifglxE8DjsR+IHqtPOjNW+vM61Y+snOVTn+U/B9+qq6FN+vh1vo9qr6P7kuj+8q3+trPz/MOC2MFvg== +1438613880.950,1.000,0.000,HISTggAAANB42u1USwpCMQxskr60BcELeBev5BUEb+BFPYIL+1wMDMEnikhmE9p0ZtL0c7hcz6XsT+UBm1Fm1OOtJBKJRCKR+E1ItuAv+iAb8wp5ITyB9YyPPCP8db4Sn2XGRnxRr5K6KvgZ6C/Ebx37jB34jfCZfic+A+rthO8Qd6DjsN8O+Ur2M8BnQN5Bp5FzjfqgoGfkPmCfPbg3NbjH0bwRXXavjeyf+WH9rB7GNzJW8i4KORfmH70TDd7/q/+mBv+LbOR96z+XN9fJh3Sf+TsqKQVP +1438613881.950,1.000,0.001,HISTggAAAMh42u2XwQ3CMAxF0yR1W1rBAt2F2ZDYgEUYjRE4kNOXnlwoEhz8L1+1nW/HiSN1vd4uKZ3u6YXSuGucz48UCAQCgUAgEPgcnfDW+L36nt3TobjcuEpcBX8RVn+F+F64wvfQeJT1E+gb6PWip/UcRHcQvWPjGfIVyKP1T7JuFv/o1LvIepM46qdBPwzqW+A8zTmvTvpB96jA/4k590XtBjqkr/OQN/ppvxlY69D5onktkD879nfnn/a59x38l/hfv/Nfz/8ERsgFxA== +1438613882.950,1.000,0.000,HISTggAAAMZ42u1WSQrDMAyUvDtQ+oEe+pO8rdAf5KN5Qg8VPQwMSiGFHjSXQc5YGtuK8e25PUSud3kjG6txWncJBAKBQCAQCPwP9ORxJbrPexDeiQm+F+Ds5MN5FeJm3EGfQZ+BMV8jeszfQdfJuhupi/UmxMN4Mb7A/AH1BviYxOcC8XDGG/E9yTlU8FWd/VQyjvnUiTG/EN9eX7FzKqSP1amfnP5OZB7qCunb4uwn1vH8yJf+vHsC/385qD/7Pks/vlf14H7qC8FgBQ8= +1438613883.950,1.000,0.001,HISTggAAANF42u1WyQ3DQAiEvbCdRxpIL6nNUjpIcy7DJeQR+zPSiFWyyYv5jFjYWbAA+fZ4riLXTd7IB+vB6b5LIBAIBAKBQGAc0of3FBjPEznvtQX+BzFf5ObkW0CvEZ1M7ELqbiSP0zbgCtxA/7RnsDGuQn4LxGV49wL3ZtAzsCfghXyPBn4j9VWSN75jRJ/Vb6TuCeIS6Sf0F2DWH+r0Y3H6xiBeib4Sf+qcCyVzwPrdq1Od+WT5sv2QnX2hzv6RTr8O3m8yaG9+q/er+uVfeb0Az84FvA== +1438613884.950,1.000,0.001,HISTggAAAM142u1WWwoCMQxskr5WETyAd9lr+St4Ay/qEfyw/gwMqYsgSuZnIJ2k2TZJ93S9XVI6ntMTNlgG63pPgUAgEAgEAv8A+dF8ZKOfTvqLY1fHLsSe4f9Sgaujw/UCOgP7S9fAvwAL0VXYfwF7I7wb3IEbyS+TuJX4N8hnT3QHJ26Dc+vkfDs5zwXsndxfJ3WQyf0mWMc6KmTdCCvRsX5Qp28K0ZlT/xgnO3qb7FMjcWfngGycP+J8/+x8enf/b81/Nv8+NZ/jXST5PACllgVx +1438613885.950,1.000,0.001,HISTggAAAM942u1WyQ0CMQy0nTh7iAcF0Au1IdEBzW0ZlMCD5DPSkN19AFp5Plac8RE71+X+uImcF3kjValV2vUpgUAgEAgEPkMPkq9GKw+x39b210C2+Uz0aJfx30j4XmWB/2azn4CXwJ+TODPhY14F/CCvzY+gz0RfQE4wHiBeG59IPQaQ1qlDJnYO+TqJh+OZ8J2ss+eP1T91+oD9x/VLpz55pZ2R/ZQ6vETOiUIflNg5yVOgTrbxnBuJKzvvAdnp91fvqH45jv7Z+uM93Mh/Aaz7Bb4= +1438613886.950,1.000,0.001,HISTggAAANV42u1WQQ7CMAxrmnRdgQPc+QtvQ+IHfJQncKBcLFmZ6CahKb5EjVLbidau18fzntLlnD7QHqXHfHulQCAQCAQCgS0hO9WTlfWyk0e9PDgHIWsjOkrqG3lnfnlm4MmQR73aY4H6I9QXWFfwg7oG/ieoQ71G+lGiP4PPQnxViIy3EZ5TjwcyRyP9KPFbnXoFvYnMle0zkk/kuxDQMaInJG+EN0OfSnzjOVOHX4kf1pd3votzv4hzL3j7lsJW4vl1nyyc32i/upP/0NZ88udzGO77DcJBBQc= +1438613887.950,1.000,0.001,HISTggAAANR42u1Wyw1CMQyL+wXehQEYgg2YDYkNWJQRONBeLFltkfgc4kuUvtRO89Kop9v9anY82wuxWTQbLg9zOBwOh8PhcHwPgd5jq8Cib+odSFbpcL5R8Pa43Gwiv8cV8hOtd52NfN63a7YK/Srik9BPtK/zHEg/E1+3e9pfBN9G+pV4ijhvpXz4fFnUMwmeKP4TBnXPon8C8XH/RPE9irgg7g3rqD4Mk30PUSeVH0QcRH5KD6LuKg6DezrK10RdMTkHsDi3ZufSr4E/43m3nuHDeeIJzl8FAw== +1438613888.950,1.000,0.001,HISTggAAANd42u2XwQ3CMAxFEydNSLkwALswWyU2YAnGYwQOtJcnfbWRiqiE/8Wq/b/tOFHTXu+PKYTLM3yQZhtna7dXcDgcDofD8d+IPoJDzCVujBuelVUwkUfVU3b5rqzIl4SteFb8E+oU2CWeoSsib0N8QD8j8jb4qSvQF9Q7Ix/7HlGnQcf5DNBV6Cp4al+K4CX4B8E3MXdbiZNXxXlOKzoT+xw6z2/orKf8JvLlFT91Wcyjdx3kZ8E38V+o8scvvx9/df/Eg/Sx99yscz1b74240z529/UGtFsFog== +1438613889.950,1.000,0.001,HISTggAAAOV42u1XWw7CMAxLmrU8dgE+uAtcDYkbcFGOwAfbjyUrXYUESPZPlC32kipJtfP9cTM7Xe2NWKwvtlyeJgiCIAiCIGyHD8b5YDzjlU4e0ymJnsPzAL9B3A7eT8BDi/GrX4Ffku866M7AX/X2YAP0AniYT4BfyXdWeyC8Bvroz+BjXg30K8Szunek7kbqbtAfWMeRnEOQvkC+A89Jn2Af1qQvUcc6dZ3UgedipC8y/SBzHMm81mR+S1KPdc6/JflNG/WyfVcG92vvXvVB/U/t81+9l/79HtU5CoIg6H9LeX6hzhf8ggVC +1438613890.950,1.000,0.001,HISTggAAANJ42u1WORLCMAyUfOZq6fgLb2OGH/AhnsQTKIibZXacdEB2G43i9UqWZTvn2/1qdnrYG3G1vtpweZogCIIgCMIvwf8kru/U//iP26iHvEDGm00bx5ufIb8MvGZH4BXgD+A3/QrzB9CtkG+BOBX4EfRQZ+nEmyBOgrxH8DPRm2H+TOpXSL0nsi+RrB+/o/5C6pXIegLJN3RsJn2ZdvKxj530QSJ9bZ08MR+sH/KMxHESF/fLOueanWcjOt7R8Z33xrffz0d/r1R3QRCEg7wrL3STBcU= +1438613891.950,1.000,0.001,HISTggAAANx42u1XMQ7CMAy04yZNgI2Vv/RtSPyAhWfyBAbS5aSTAZWog285xb7ajpuk6eV2v4qcH/KGddbOaXlKIBAIBAKBwB6hg5/b27wS6JXE8eyMMf5E/Ov9MTv1TcAG4+T4D51b5wJ5sQ70184zxJ1B18Beob7VfiR6I/mw7hnmZTA+QZwCdiP5E9gb0RfQF9LHCv3K5H1mYOwrrhtkIevBwJ/ZfwvozMnnrWeFsZG4SvQsbvqwPnH2o7dfvX7o4HNSN9Kpc75tPY9v8/0ad9T3SwflCQQCf7gfvgA9QQWu +1438613892.950,1.000,0.001,HISTggAAAMx42u2XQQ4CIQxFC4Vh1IUX8C6ezcQbeBGP5hFcOLN5yQ8jk+imf9MU+tsPFBIu98fN7Py0D3yxabH5+rJAIBAIBAKBwP+QBuNpc4fniEui/tb8ijcJXsU8xzN0lsUeELf6R4zP4DfEO+o38KlvBn/1T/AL9BfMM0+D/iLiJuiiXuqoyEefOpv4H5TOuVRhXdTleSbRjyqPiXW46O8idKu+rkKPizrs+16d0uF9e8+8c/96ft4Yv/edssH8eed7aIPr+9U+DO/fG5r5BZw= +1438613893.950,1.000,0.000,HISTggAAAM942u2Wyw0CMQxEnT+BAw3QC7Uh0QEXyqQEDmxAetIoCO2Ki+di2Y5jz67j5HS9XcyOd3shLTIsMp4f5nA4HA6Hw+H4vI+2Wr92fJxIIgt/gp3vxTDxF/XOhD1h/VjXRHyHbOCh9ASd9VXEDXmAn/kr9h36DrLATz0ibxX+PerqgkcR9an/yry0d/FduX8TfNhv7KMk6lP9wr7Ioo8i7Cq/6uv85TmNoh4Vp845+ZmoyybnajYfgtAVrxn/sPI8CxvNx195/Os+ecc9AYy+BXc= +1438613894.950,1.000,0.000,HISTggAAAMJ42u1UwRHCMAyL7TYpPGABdmEd1uCODVi0I/RB8tGdToUfPevjiy0rSpP69no/S7k+ygfRo/Xo97UkEolEIpFIHAF20HP4Tn6IPK4D9HHthD90ZogjXyE/QT/jO+Qn4I96I3WWD5If8dTjAusz+Guk70J8VOAF6DbYl/lBX/hdK/GJ9UX04/th9+fiPc3EjxEe6hm5N6w76S/knTnRVf+LE79KT/2fLs6n5pmL/WznfGB6yod9yft1/v37XLcNAMIFYA== +1438613895.950,1.000,0.013,HISTggAAAP542u3YQW7CMBAAQNtJSIEv8Beu/RYSH0B8tD9oDySXlVamhQOoM5dVnPXajp0gcThfT6VcPsvNsMR6C8fv41cBAPiP6ovUqXe21yRm94ckryX5a/sYYkvqbZY4LXEbrmPeEO4PyTj7zrw24XoO9WLdOO85zHdO6kxJ3Cf94nOL898m48R1xby1fZesa837SPZl7uzDLtm3qdOvJe1jcg7bH69rsr8lec6/PefZ+Y7jxPM2ddbZe19bMk7pvNex/tip25K8+uTv4KPfx/qkfn7PHmsHAAAAAOA1+B8XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIB3Un8Ap8MG6Q== +1438613896.950,1.000,0.000,HISTggAAAM142u2WSw7CMAxEnThtISy4AAtuwtmQuAEX5QgsaFg8yXIJQWLh2YyS+DOTT9XT7X4VOZ7lBV05rZwvDwkEAoFAIBD4B6TBcb/W0Qvt1JOc+ff/neOnjQviyQl6Z+TRD3lC/M6owz6t/2LombFejH5tvEdexfwEfQXr1F/Rv6LOAj6g3uL4YH/qnA1Ww8+EfSzGPSjOfmbMk9U4RzXuZ3bOn36SodvSyXzp1KuOj62+ivE+9cP3LE6+bBznQd/BUXHWvn5bL/XmPwFE6AUg +1438613897.950,1.000,0.000,HISTggAAAMN42u2WTQoCMQyFm7SdsSp4Ae8y13IreAMv6hFcOCI8+AizURd5m5DOe/lh0tDz7X4t5XQpL9TV2mp9eZREIpFIJBKJxAf2J3k80Km+BTrSV3gnNno/wvcW8Lrw1Z9XO8n5299J3Ems6o8Qp0q8Jn4P6hiiG6Dfi9U+B+SbgroOAZ9sh//Wge/gO+gpvvbdYf7mYN5ozmh+O/At6KfKuQV1UX0OfPLp3hOf8lhw323jfoji+ca9aT/ev16+A+rDnzo1BXQ= +1438613898.950,1.000,0.001,HISTggAAANl42u1WwQ3CMAyMHTdJ4cMADMEGzIbEBizKCDxIPiedXBUq5eH7WHFz9tm13F6fr0dKl1v6Incr3er9nQKBQCAQCASOhEymQybTLT/yslOnF3/wbfwfAm8h8Q34rdsCvELuKzmfgF/BGpwz0ZFJnAy6MJ85foyLdaLeoWcl/ajwvEE/UH8h77USnWfwr04fC+RHHQ34WI+BLiVzxuZViR/n3MgcZ5LPnHiLk9+Lo0SPOPUI4SfnnpA+sz2hDn/rvhAnP6tnb769e/Ko/S0b88zy3ftb3R9cGAUd +1438613899.950,1.000,0.001,HISTggAAAM142u1XwQ0CMQxrmjRHEYgF2IXZkNiApRiHEXjQ+1iyUoQOPvHHSs9NclYbqefb/VrK6VHe0MEyuF6eJZFIJBKJRCIxD/mRfpZXWFAX91XCSuIGdYToG6wb+d4H7wYvoDPQNdCv9R32N9LHQnQOOgc+Dt6Tvh1iXG/EvwOph/kUYoP/cPCjE1+wLyM+sXNhk3GBdTwnSt4l8mE/eI57kFdJHczrQR4J7gO7Z1FfSvxDH9i8qMG8kUAfzYtv55v8ea5uNe9n/d6svxclPwXN +1438613900.950,1.000,0.001,HISTggAAANR42u1WMQ6DMAyMHQIGts79S7/FWqk/6Ed5AkPDctLJtE274Fss4ovPDomT6+N5T+mypBdytVKt3tYUCAQCgUAg8A0k6v0I+qaOOPrMr0RP4H2YyXiP70fCG6otMG7A3+N1YDHPHuLtvAl4E/hRxwhvhLwN/EbymKG+mfCZvoHFenE9Mb+BrGNx1q3A/Ezi4H5Q8o3jqK9OXBaf6Yizn718M5k3Qv5H46tz/jLZl51znti5Pqrn9QNp1M+0UR+Vxn31X/eU/Ih/tvs03icn+x8b1kAFdA== +1438613901.950,1.000,0.000,HISTggAAANR42u2WwQ3CMAxF4yRNCj2wALuwEisgsQGLMgIHmsuTvkxVDgj5Xywn3/6Om7o93x+3lE7X9EZZra02X54pEAgEAoHAf8KiBT/db4+fwTMRp9aLyFexTx2lm5GvYp//mWN/Wm2HT/3hN6wzXtXXwJuFHuuqiB9xC/J18BfwO/IcoEO/iXN18Hm+hvWhf3T6PIl6TDzH6tRZ4fM5Kpud+qpzL9T9zeJeZqHLfLPQZX8YX5z3rTg8c/Rs4xzw5kj+MM42ziv7Mi/trMt2zt+vfx9edUYFbA== +1438613902.950,1.000,0.001,HISTggAAANJ42u1W2w3CMAy0nTgl9IcB2IXZKjEAEosyAh8kPyed0iKkgvD9nPyIz4kaN+frfRE53eSF1Fgb2+UhgUAgEAgEAv8M3amerszXAXeU/r4jcfQbqWfABeKF5NlAxxtPYDu8UzPkHSAP44nUnYi/kHhtfCTruu5M+neiM8M5ZIhjP6hfyDrsr0JeBd1E6jnpp5D9C+gb2V96kxVsBz/Wx3viZN/s+5ZBnhC9TGwb3GNbeR+Zrm20WZ3R3NKVc2jr/Pv0vN37P6Ff1tevnr8+AeGzBYg= +1438613903.950,1.000,0.001,HISTggAAANJ42u1X2w3CMAy0nbgPEGIBdmE2JDZgOcZgBD6ohXTSyaio6o/v5xTH9jlJ47aX++Mmcn7KB21hXdiuLykUCoVCoVAofKEb+evG9cZ3ngErjDM/IXE4HsDeIT9yzDuMDXTD/wg6DvNhn4Cd6EW+kbBD3sPCMzD6z6A3wrrCfiL75CT/RNaPdiN6AzmvTvyVnKsT/07OX5P8LXkOG/lvkUQf68U6HPJYopPFsXvTSFwjdVnSH5TcExavf/Yd3Je1/UhX+uuP9e39fti771O9N8qKBbA= +1438613904.950,1.000,0.000,HISTggAAAMB42u1WwQ2DMAy0nThQaNUF2KWzVWKDLtoR+iB8TjoZpErtw/exHMf2cTiQZX09Re6jbCjdarf2eEsikUgkEonEGWhK8FPdsI7BOutj7D5I8hziuK9CPSf1ncT3++kAfiF9PfAL1POAL/K4QP4A6zPsvwX5rdsrWCd9GtSboK8H/J3oWYmuDeKV9GnkfQjkGeFngR/NrR6cP+SBuiqZGw38cvLcRfpUUoc9tx48/xbwEMLr29+rf/0/0Pn6ABhABO4= +1438613905.950,1.000,0.000,HISTggAAAM142u1WuQ3DMAykqM/O4wm8S2YLkA28pMuMkCJic8CBcVKk4TUHid9ZlAivj+0usuzyRh6cBuvtKYFAIBAIBAKB/yF9Gafk/86rg/7GdXABPwWusDZ0oitDHNYxPhNdlneCeNy3+EZ0Fogr4HcZfAW/GfTMTnyH755gv4NOy3MCO+pYyDk2qNdIH/HcKulTJvlYPzM5D+wvu0esrhIu5J4Vssb7pMSu5H0lR1cmeZS8P2YXxz8RO2P50N+bL95++nGOycH5dbSOvgD8aQXs +1438613906.950,1.000,0.001,HISTggAAAMZ42u1WSQ7CMBCbpNlaDnyAv/A2JH7AJ3vsEziQXFxZBqEKIY0vVjvOZLZEudwfN7Pzai9MnUPneN3M4XA4HA6Hw/E/iPCuQwTCu3eg8Ds4dc5EH0GHfgvRF7Kugr1A3BhPBR72BfwuYB88w/eJxJdBX0HfiD1DPWfQI1eybyN1iSKfRPpZwD/TNTEPifQxkb6bmCM1X+/O9UQ4CD2Lk61n5y4JfRb5sTqxvNX5N/HfxH3yrS4c5D98eH8erf+1390+TzUzBcM= +1438613907.950,1.000,0.001,HISTggAAANV42u2XUQqDMBBEdxM1SlvoAXoXe7VCb9CL9gj9qKHwYDAWwZ+dn+A6uzObxKi35+thdr3bF3kZfRnT/LZAIBAIBAKBwA9+kG6Cvm/06Ss8b8xz+BmWscP9gnjC92YW8YS8HteVdxL1MvgFPgfUHUT9CToF8epzRJ0JdWreWfQ1glegOwpezb+IPifwevgirxP+2AfXh/1nMZ/8z+B6J5HvjTzuN1vZt1nEW5+HJPRUPWv05Y1+lb+y0/mlfLTybeO87n0++599H/2+OMrn3vr+AchgBS4= +1438613908.950,1.000,0.000,HISTggAAAM142u1WMQ7CMAy046RJBBIf4C+8DYmBnY/yBAbq5dDJZaISvsVqfT7brR3lfHtcRU53ecNWq6stl6ckEolEIpFI/AN0Z/WUL+tTvMcRPeYX4rdAx/0V/KjvvAX8HteInufp5L46gd+JjtsD1LFA/gHPyDPgFaijA78T/QF6zjuSviboTOBXEjdIXY301aAv5DfynyuZAyNzVYiegS2Bnm3MoySO7YVurE/J/hjJK2S+JdjLSE+CPiSIj95vPXei+F+fs7pz/Y/v9QIvuAVg +1438613909.950,1.000,0.001,HISTggAAANR42u2WMQ7CMAxFY7tt2sLCzl24AldC4gZclCMwkC5PslzoQoX/8pXI/nZjx+n5/riVcrqWN6yxNNbLsyQSiUQikdgXZKd5SJbuK+hGewGbU4/Fr8daAu7ABn9z4neIt/Ch8ejokyvWM+JV6FfoHxtP2Ke9Ib8B+wadGToT/AbsMy/aVdjzHBTrEcw8q1On3vl+79zV6TvWfXDimdMXnl4PO3Hy0KA/u+D+mNPvGsw1z68E93Lt/Iy4BOeydh5/mrd8qKM/Nmdlo1++b//9fyQvWuwFOA== +1438613910.950,1.000,0.000,HISTggAAAM942u1WwQ3CMAy0HbehFIkFGIQfsyGxAYsyAg8SHiedXIoEj/o+JyfO2UkcK6fb/SpyPMsLpbE2tstDEolEIpFI/Be68fhbv2f98D50od3/fdbYg/kB/4nAlYwrrB8gXoV4I9i4roLfRPwwTuc9rN+BTtefwa4kLwOd7neAcTyXkexjJvudSJ5RfnieDjaOYz1gnuhXAjuK50HdCLlXVn+FMKtrJbYGOgrnYpCvk3040ROiz94xxivB/Np+EunYyr60FPZl//tV337zE1AZBTM= +1438613911.950,1.000,0.001,HISTggAAAMp42u2WwQ2DMAxFbUhSQg9doLt0NqRu0IU6UkfooeHypK8gRDn5X74c298OOFHuz9didnvbD2Njbzw8PhYIBAKBQCAQOB++05/4noO9vveyiBugw3XmJ/GOdKFzaVzgn6FTwI78BHtqXLFOvRnxGf4K/yj6JWfUpc1+quhn/U5XxDOf+y2CM/LU/01ifiaRT92EuqOYg63zp+ZOzbGKZ12D3dNJwm8b9+Mbz6WJvr1zrvzge6TXX0/HT77vjtLfq+N/rre7zy+VnAXB +1438613912.950,1.000,0.000,HISTggAAAMV42u1Wyw3CMAyN7ZAmLWICdulsSGzQRToaI3DAuTzpKUWUm9/lyY5/+djK/bk9Urrt6QNzFmddXykQCAQCgUAg8H/IyXZ60F/x/0fs2Drj4pzhn9nliei7X3O+ACv4Z1jHvJi/gX91XkCeQcb4ldRbiF0jdhPE7/VeQW5Q1wz7WuB8jMQ1IhdyThiH3Vsm+dh7NKJn7xHzCNmvQTysq4JeST0sDztfHfShDvKN+i8N+nHU5/LlPJGD80VOmjs/z883RWUFpA== +1438613913.950,1.000,0.000,HISTggAAAMR42u1W2w3CMAz0o05b8cEC7MJKrFCJDViUEfgg/Jx0ciqBQMj3c6od2+fWSXq63jaR40We8M7a2c53KRQKhUKhUHgH9M/06Yd1aGLPmMUZ/PcJ2NHvJI+TPJ7oevmD5FnBPwEv4J87N7AHWRdEx4HUb8CoZyY6UC8+L0k97H8l9Vh/kbxHIfHIWJd9R4P8DnZc15J5CzJfE6nH5t9JnCX7wnfuJxvUle0fVs9IX1kcgw2eG7923uugXXfm+fb9pA94dAVS +1438613914.950,1.000,0.000,HISTggAAAMp42u1Xyw3CMAyN7ZCkigQLsAsrsQISG7BoR+iBcnnoyRTlEsnvEtnx5znOp70+X4+ULvf0hu2j7KPe1hQIBAKBQGBOSCzBFOslB2U9GNerSx29gfyJl/G7EfQn8M/ErxK7DnEqjJnYN6ijgL6D3xniNpAX4Id2DXh2EqcCH1YX5umkbuRRoA8LySOgz04/9cf9wPpdyLxBn5CHknlz+LF9mtj/DrFndkbOF8uvzjlUsh466L4QEk/+vPdkkH62d0oG8/3q0wa3swVZ +1438613915.950,1.000,0.001,HISTggAAAMh42u1WwQ3DIAy0DYY0/XSADtJfZqvUDbJoRuij9HPSiVBVSh6+zwk7PmPAkPtrfYrcHvJBaqyNbdkkEAgEAoFAYA80luAvsB/XG//jkI3Yhfhz53v0J+Cv30l8BXuFuNzRvTSewV5Ab2p8BZ6gbof4AvYKeRPRw/wVdGYyP4cx03MyzqQO9GOcQz3W0c9Qh5N9Qj08n4X4cZ+NcCF52PnvnSsn/adkXrKzP0b7Wwf7PQ3q2eB9pAfd80e/J3bSOs7+zuobcrQFKg== +1438613916.950,1.000,0.001,HISTggAAAMt42u1WSQ7CMAz0kqahHPgAf+FtSPyAT3LkCRzwaaSRiVShHjyXURJvdcZRr4/nXeTyki88WIPt9pZCoVAoFAqFwn7Qnf1ZPANWwsL+A4Ex3xLcwN9JHgf7BvGd2A1Yr8FbcId6TuR8JXZn2Mfv2sC/Q10Yr4Ofw74ldWO9A9YG9qyPA7gDN7gfvBfsQyd6aokejOTzpO+W6IjpyUk/lMS1RPfsXBL9GpmzhcyTTs63/zj/muSbfWcyP5t8x+xg763KMfCvOvQDh/UFzQ== +1438613917.950,1.000,0.001,HISTggAAANJ42u1UyQ0CMQz0kd1s4EMB9EJtSHRAo5Swj918Rho5RPDzfCwfYztO4vvr/RS5feSAn1JPaY/uSSQSiUQikUj8Ahroo/xRnpF4tDtIrFfAvxC+EamE32WD+BXqKtg34BfwX6Fej68Qt0KeyN75F9Ab4VcS56DjeTaSr0E+nJcH88H5V3KfBnYnczHIj/dgpH4h74PVX0j/Rt4J04W8RyFzKKQvC/pVIkvwry347xr889G9M8uL+hjdbzq533RyD367Z0fPM5tX/pQnMTn/HT4LBeE= +1438613918.950,1.000,0.000,HISTggAAAMp42u1XSQ7CMAz0krYpIPgAf+FtSPygn+yRJ3AguYw0cipx4OC5jFK704mTWOn9tT1Frrt84Y21sT3ekkgkEolEIpH4HyiwkHGHAeugTsF7IXCPT3CPrEQX85W8b6DTn8+Q3/UW0KuQbzCuoFNJfAWuwOfGJ9C7gS8negv4c9Bh31/Br4MeqwfW7ULW28l6TCS/kP3F4jP77wj2qQfxQvYVm58RH070I39Yp2ieOnhO5eC51aBvSOBfg/5jP+5joz6P6gz7+ADN8gXV +1438613919.950,1.000,0.001,HISTggAAANB42u1XQRLCIAwkaQJUPfgA/+LbnOkP+hGf5hM8iJed2aF1bPWQveyEQJamEOAyzbeUzvf0wtBYGuv1kQKBQCAQCASWQCIFm+RNOv2YX4ktZLwSuzY28OeVcd62QzyDe6gDZ+hXgY/gH4FPjQv5ngzs0G+E8Q7taGO8QvQOJI84z0LyUUkeGTvoOrn/585/GICN2NpZD4wxvrJ3CmlnOrh+DfJuRNeIXiL+pfuAzdM7+1q+VG9kZT2SDu99DsjOep/Wcf2x/r+dy1vNR56ywQWh +1438613920.950,1.000,0.001,HISTggAAAMV42u1W2w3CMAyMHykJCLEAuzAbEht0kY7GCHyQ/Jx0MkhIgOT7ObW279zUSXu+rddSTlt5wgbLYL3cSyKRSCQSiUTi+5A340ruGzDWa+Djgyvx2wFPnw55nfx/LuBjoIf+TvKd+BvUI0+fA+jsIa9DvMF1B71G6hZSb8Rv8jFYh0r8Hd5DhT4qPG+DuEF9NA/oZ0FcSH9MpwZzgrpsTpzMo5N1UsIS7Ndon1mwjzXQlaCPV3UY9EPn06+eo3/7fXgAD5cFqw== +1438613921.950,1.000,0.000,HISTggAAAMR42u1WyQ3DMAyTbMc5GqALdIBs0dkKZIMs2hH6qPMhQNAo8uhD/BCOZFESZCGP/XiZ3Tf7Ijf2xun5tkAgEAgEAoHA/8MFJzjjvUz+BxGJxEOdgcQvwInYR+C58QT+57kCY9wR7Of3G9GpUMcq/BawL516mfRthfoG4JnUz/KbSJ+xPw46mfSD9bUKHayzED8X86bmz4gezjmb0wR5ufDL4l0Vkad31mPiXTrosT3RG0/tjV/jq/111T68yo/e/wDzNQUl +1438613922.950,1.000,0.000,HISTggAAAMV42u2WwQ3CMAxF4zRpWnpgAXZhtSKxAYsyAgfC5UlfpioIDv4XS7bzv53EUU7X2yWl45qeGLq1bvP5ngKBQCAQCAQCv4M5/uz4TfAU+Cv+g4NYX5w88ihe1qf4JuS/4iMs9RriWf13kdegOwn/Af6K/SngXbqdEWedFToV8Rm2ib4H6Czo19MfBU+GfnL6z859M5Fv4pzIx/0ycd7lzXkZd9ZXnHgSc8H5ZP1q/m3je5I3rrOdeval9+/f3udP12kPubYFZQ== +1438613923.950,1.000,0.001,HISTggAAAMB42u1WOQ4CMQz0EZINFHyAv/C2lfgBH6WkpCA0I42iCHkrT2M5dsYTW17t7fHcRa5v+cKH1WHt/pJEIpFIJBKJRBwM/r+i4ZP4T0eZ6DKi30m9Aj7aE7GN8DewRu5XqN/A78NuwFPhvAHvBnln8C+Qj7o71EN9Fe4X0g/M71C/TvqC73DSt0L4bDJHJzw4TyO+Ej4lcabHCb/9GXeyN07m64t7X0j+bC/loO+KLp5H8WnQuzUoXw/Ss8z/AY4wBdk= +1438613924.950,1.000,0.000,HISTggAAAM142u2Wyw3CQAxEvd5PEjjQAL3QBu0g0QGNUgIHksuTRo4EuXku1jpjzyTxrvb6fD3MLnf7oq6xrNFvb0skEolEIpFIHIfy5/rC+9xOPfIb7ocdfOZZv8UJ/Rz8hjiw7tCrIj9hvYg4gz+h36Z7EjodPhfwZ+Qr8gNx0z+jfxc+F3xH9quB/1nosB/rHGtDvgteE304hyOYWxf/wYWPEcwl58/Ec/powo/ad0XUmfie0T72YN/7zvMi4peDzic7SO9Xn/96P4/O2Q9vMwU3 +1438613925.950,1.000,0.000,HISTggAAAM542u2WQQoCMQxFm7SdqQ7iBbzLnE3wBm48pkdwYWfz4NMBBRHyN580aX+Tadq53O7XlM6P9EbubJ19faZAIBAIBAKBwO9h/E8TcRm2C7+a70JP6Wdw7VyEn/obz50XrDNh3mHgL/BnxDnsBfpH6DTBE+bVQV6M23ROYMbRboivYMP++B24f6VTRF0bbBd1LqIeWZwjMvdhIr8Z4+r883yyr4rI20Qd9vZdGfSPi/VtZ51scF+4GFe26v80yNu+fM+N7r9P1/vbd+AFP28FoQ== +1438613926.950,1.000,0.000,HISTggAAAM542u1WyQ0CMQy0nXNBiAbohbb4ItEBjVICD5LPSCOzu/DzfEbxMXE2ibOXx/Mucr7JB2mwDrbrSwKBQCAQCAQC66Er7cyvf6qLsZD/wgSMKIMzjFG/gb9C3uQF/BV0Kuh0Yi/EbpDXiV4m+ZXMO8cnWO/UOZD4RtZZIG9x6i1kP3D/mrOvRuosEG/kOx0hrhN9IToKdnX8zckzcr4zObcGnIjdnHubyD1Izj01J16desRZv63sV/plf9KNfXFvX/31fLazTtv4DugbMSMFag== +1438613927.950,1.000,0.001,HISTggAAAMl42u2VwQ3CMAxFbSehhRMDsAuzIbEByzAWI3CgvTzpK74gOPhfrHx//7qJm17uj5vZ+WkftC36FuP6skKhUCgUCoXC7+HJfIAPkadfE/xAXUBvgmdcsFa+Dn0XfgO6BfwB6w4+hI7Ppe8q9Dt/Svazgl9QT30Tfux3j0fEBv8u9p2+PjkXh38X9UPoupgfF+fPOVZzxXlR80uefXG+h+ijJb9H9V42WUfyPnCRV9+/T3wj6Z/Nz/bHkn3ZpP7b923hT/+Tb/gJBaE= +1438613928.950,1.000,0.001,HISTggAAAM542u2WSw7CMAxE4zhpUxBI7HsXtlwLiRtwUY7AgnbzpFH4LpA8m9GkiWM7jtP5cj2ndDilB3xhWzgfbykQCAQCgUDgHVik4Cf5VJzFeA8F8x32CpjrOF/p1X4FO/S6rkEPsDNiXHGFvzthX/m3wfyVJ/Ae+zTBFf5voRu0ykeDfxPiG8W+PNcqzrMiL/w+iHgYv4k6K/CH9VJe1FXUm4tz9o7u1XsS9887cZqIO4t7q+5x/rDv2pN9Ru1rnTzkL/e7eMfif+Ev47oDnVwFWA== +1438613929.950,1.000,0.006,HISTggAAAPZ42u3Wy03EMBAA0IwTb7ILJSCxndAQTSDRAY3SARyWXEYaWXtYfnrvYtke2yN/ojy8vr1M0/N5upi/yrgUjx9P7xMA8D/FN4/jNufVBvFRxEf6/5sH8a2I29sPRfySyij6I9XXNG9P9bnIay7y6UX9mNY7Fnnfp/Fr6u9p/Z769/atiN/zPRXzHtL4u7Rvp2LdpRi/DvJtRX5bcX/yvubzi8F59SKPXqwTg/swOv8l3fNtcH/boJzTfMvg3cXgvbaif9TeBu94uvJ7ET/8fbt2XNw4n/il8wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf1d8ApriBnM= +1438613930.950,1.000,0.001,HISTggAAANR42u1WwQ3CMAyM7bhp4cMA7MJsSGzAYozCCDxIPied0tJWAuH7WJfEZ8dtLJ9v92tKp0d6w6qVavXyTIFAIBAIBAJrIH+Wh2wcf64ecp25Lzj/VZuJTuNO/JvfQHSczJ3ob8CdxG26hejmjk7p5Hsg5wz4EfhU7QjrhfBM9B32J/AbCXfiP5B1gXUl9cukvk7ua+R7KoljJJ/c+U+tk7eSfPTD+xu8CyNxlJxXUj+sa+89K9lPJO7SfqMLz8tO/WxtX9+6/8uX6fz6fLB3HeQF8LUFtw== +1438613931.950,1.000,0.001,HISTggAAAMp42u2WwQ3CMAxF7TgtqUCIAdiF2ZDYgGUYixE4EC5feipFXFr5X76c2s633aQ93+5Xs9PD3ojO3rlcnpZIJBKJRCKxZvjG6ikL6/Mv4xw4JN7hv7EKh/Ag+1V47mAb6KniX4Q/6yPwrvPUuQlXyHMUu4neEeL34j/BusY30TuATvVz6OsB5knzC/BT/xH0BfSxQF6H/GXmvdB9VGeZ0a99CogvEBdwXrVuQsDcbKHtcP4d+mwz+9qfdK3tnv1V91rq3dr30V/nBAWk +1438613932.950,1.000,0.001,HISTggAAANx42u1WSQ7CMAz0ko32wgP4C29D4gd8gufxBA60l5FGSYq4IM9l1NgeO2ns9nJ/3ETOT/nAN9aN7fqSQCAQCAQCgcA49GCcTer7ZF78z9POOnICvdzRMWDvcCHxBTgR3u0V4iv47c+NrGdixzoWsK+kzkzyruDn4FfI+S1gd9A9AWfww7oy6DXyXrCeRuxK8uF9SMTPB++rkfNGfSX7NZJfSH85qaOSvJXoYJ2sX1gfGul3Ng+sMx9YPhmcC2wfvfmlg/NTv5yrs/NaD8aN6v1qH//2PRzWfwMDWgWy +1438613933.950,1.000,0.001,HISTggAAANV42u1WuQ3DMAwkqddxlzq7ZDYD2SCLeDSPkCJyc8BBhhwXAXjNQRR1JAWK0OP1XkTuq3wRGmtje27icDgcDofD4ehDT/rpYDxkAz/r+MdOHrhOwEbiJPhfJvLfZPt7XhnsmdgLcCa8696I7gQ6kdhr45nYJ9iv4Mfqx7iVrBPYDeJi3YnwDPkU0EP9QOxK+kKJXgE9I/dinb5heoHEDZ0+Z+8okrqFxGV5CMnHSF5Yv5B9GXzXOjhnjp6zg3qjc/DX8/kqnbN16sX343D89f/rAxhZBas= +1438613934.950,1.000,0.000,HISTggAAAMt42u2UwQ3CMAxFk9hJQy8swC6sxApIbMCijMCB5vKlpxQEEgf/i2XH3/l1HZ9u92tKx0t6wTabN1vOjxQIBAKBQCAQ+B3yTl/jBnlF4mVSJ090OdQrcD50NfAd9Go+6W5yXsUqf/hd8vS+oesg8Sr8LvwOeavU7XDvIlb1uvCoT1XyF4mv0O8q+U36Td/noM92/tcGc2GTOTHQ6cBzsAbvRXk0hz6JF+An6SfVp3daP3zXtB9oz8z2yN66396Hs734L3v8bf4TKrUFUw== +1438613935.950,1.000,0.001,HISTggAAAMx42u1WSQ7CMAz0UqdlVR/AX3gbEj/gozyBS3oZaeSAOCDhuVheYk+aSZXL/XETWaXDu9Vu7fqUQqFQKBT+BVqfoPDDetHBvCV1E6nH/kbqHOwE65zwctLXEx4bgrxXG8Qb8NrB+oD83O2J1DWwB8gvkJ8hf0zm4rwtvifzzjDXoC/2b8RHHeD8hZw7zsF4kP0p0aFDvRL9BNGfJzyYToLcFyU6NuhjhJcRnUaSR3/0PjP+mtwnH+wnybmM+t/6v8mH/Oqd8OY+X0ZqBOs= +1438613936.950,1.000,0.001,HISTggAAAM542u2UMQ7DIAxFjYHQpEvHDr1LzlapN+hFe4QOjTp86QmqRpn8ly+M+XwM5vZ43s0uV/sgb5w29vVlgUAgEAgEAoHjkYRpnuIu8QzxX304zBcYN+EMnEC3gg89zyT5J4kXyDuDvyrjSXiWvAbrXHyrL913hvVax0X0KtSriY6LfoNzUj20nhn2WSSvdupTQL+Ab713B70K78uBE+xLfeCdd0NcOn2boR+pbgY+fPA/sU4f2uD6f/81H/yn0qDuXn73ytvL19G6X/030z0E9w== +1438613937.950,1.000,0.001,HISTggAAAM942u1WSQoDMQyzs0wyUOgH+pe+rdAf9JM99gk9NHMRCGUKuVkXY1uRHU8m5PZ8Pcyub/shD+vDpvvHAoFAIBAIBALrkcB3wXcRT0SX1XVimS76bdhK6h7vzCLqNeCj/sHroLcBr5A82gv0XSHfQRd95HWis5P9NWF30MH9VDHfCnH0cV6ZxDvkMzlnhfSVyXcpQsdh/UbOlZF1G+gqfiF89j9kso7l1fxc1E3ET4Jnoo6Jej55D6XJe8tP6hqZ2yz8T57bGqzSPd3HF0l8BdQ= +1438613938.950,1.000,0.000,HISTggAAAMl42u2WwQ3CMAxFYzcpRRxYgF1YiRWQ2IBFOwIH0stHT6YtEhf/i9XU/j+1HTeXx/NeyvlW3hi6tW79OpdEIpFIJBKJxCfsz/q+cn+m9zy6/4G/vq+wDwd+taPwVNH5Ns5l/dBtE1vFbxD/Zf0I8U38yJ90NM6AJ+LTfDX4bgfdKVhfeE/QHw3236BeFeqlPOqnPA46xO/Qzx70rQGfBXkwqJuewxH0DfJa4Tx5MBc8mAu2cq5F+dqqV34ct3VO287n3f+TFwKYBU4= +1438613939.950,1.000,0.000,HISTggAAAM142u1Vyw3CMAyNHTupxIEF2IXZkNigCzAiI/TQ9vKkJ4eCBBJ+FyuOP8+Ok1zu862U86OsqJuUTer1WRKJRCKRSCT+CfKjfGSQpwbxKomrsK4g9SBfA3+Mt+930KN9A4l6JfU5yVch776ewM5B38Cvwz7qndRp4Hci60Z4RvE7+E+kDif9Q30j/Bs5ByPnL2TOcJ6NxFMyj+x+OMnP9Eb6YIS/kLnWgC/2ld3zyF8DXnLw3spgf+1F+9F3KuL/7jut5TPQL/0jsgDMIgVz +1438613940.950,1.000,0.001,HISTggAAAM142u2WTQ4CIQyFoYVhdBZewLt4NhNv4EU9ggsZE7+kKf5kEpO+TQPtK22hwPFyPad0uKUHtMvcpZxWTSAQCAQCgcArcpTgrbrkjer7/McZevnQrkCvsBNHz/iq8f8sjn6GXRnkL4iHfiZI+lll63KH+T3WmeFPwFvgr8Ge9ZvA45jxMc8KnpW/V/8KfoMd+dwndfaN82qM1eDJoB/mV426W+dcjH5Jg33A/spGn3hSnD7LRjxWXxeHL4P3z7++D1vd079+L77O+w7nPgXW +1438613941.950,1.000,0.001,HISTggAAAM542u1WSwpCMQxM0p8VwQt4F88meAMv6tKlC+tmYEgeD3GT2YTmM0naEHq5P24i55d8UJbUJe36lEQikUgkEonE76Ab7Ur05tglaDdiL+y/CPoKeiP8Bv5tyQ586If5O6nvQOr5ngfkbRA/gGc48YP004L+J+hngv0I9gFyOvkmuedK6ing15137lBnJfdqwbORd2Xzg3NdiF5JH2z+K5l3plcSb46fOnwsb3F4GY8F940F9xPbFxLsf+te3LtX/8Wjzp6NYm88vfc3b7gF4Q== +1438613942.950,1.000,0.001,HISTggAAANR42u1XyQ3CMBDcy04CSDRAL9SGRAdpjFIogQfxZ9DI4VDEY+ezWmc8sz6ySk7X+SJyvMkTvkRdop3vkkgkEolEIvGP0Kz3Ix8jfIPvQYOoJG/8CfIAHuYF9ALmO/DweYX5FfhO1tV0dkQ3QK+Nj6DP6m/xQMbRb4D9a/ke6pjAd4C62DkY8CuZVwk/YLyQ8ylk39h9MnI+Su5nJfpC/HQlzzv3nL0HWGcQXyP+RnScnHPPXzq6yv73On1krY+/2Z90o35pP+6f+qXOVut+0X0Ax/wFoQ== +1438613943.950,1.000,0.001,HISTggAAANl42u1WSw4CIQwtrcCMunHvIbyBZzPxBl7UI7gQNi95qfOJTmLf5gXavhbowJzvj5vI6SJvWOPUWK9PCQQCgUAg8J9IwLEf8+xL49JEPS+POnYjeQziu33H/iPBjvqef+cMfhniu9+R6NfGQ+NCdAeoIzs8wrgAj6Cbod49zFeI7+MD6FXQKaQ+1Blh/tP9MbK+SubN6Q88ZyXnmZ0+wH4Q0FOSN5O+Y3H4HbA6lNQrjr+X15x4cdYvM/1YnrXfhan331Loj983XXmfZCN6srH6vr2+9AKjXwUo +1438613944.950,1.000,0.001,HISTggAAANN42u2Xuw0CMRBEvWv7bCCgAXqhtpPogCYJKYEAk4z0ZKE7JIKdZOTb33j9ke9yu68pnR/pjTzYBvv1mQKBQCAQCAQC22E7+6vdwe4Tv1k9A9Z8Ju/Jz/cyuA+uoK/AWPMmyVfEvgA3iWsSfwT7In4H4S6sdTv0oUndLHlpHieJd+lrA30mOqrwIuMuuqro1b5mWA8Hu0E87YcMnKAu7ccKetrk/ND50DwF6jn9b8H65C916Hl0mL+Dfod+OvTb/vQetZ3vW/uRzq35bK9+vQC+rwX3 +1438613945.950,1.000,0.000,HISTggAAAMl42u1WSwpCMQxM0ry2VsELeBB3nk3wBl7UI7iwD2FgiCLqJrMZ2qZJmk/J4XI9i+yP8kCZrJPtdJNEIpFIJBKJxHM+imA/tqtkrW/6ZUTOcT6E9TK5BnoM5k0n+gzO0Y9CuBH9C2G034mdBu9b7+9gv4GelTfEvy2JC8azkv0B90Zgr8P9AvJO9DG5SvIwSB6xjjqsndRPDeoF86zgj5E4a1CfrB4tYA/stuBdrI9Z/2nwD0T9LUT/p/+Ryn+gL/r7Lf/0DrH5BSI= +1438613946.950,1.000,0.001,HISTggAAAM542u2WPQ7CMAyFHbv5KSyMDNyFsyFxg160R2CgXR56cisRKiG/JUrqfHZcx+3tOT1ELld5y5YxLaPeZwmFQqFQKBT6R6WDuamTv+TYffzvORy0Y8+RX2DfOh9gXWFfBjvkjDCvsK+SODLxb4SLvEo467kbcE7ArxBPIfE14HpxjODnTPKN8+bkKRM/DdaN5K2QPNvO920b6xHreAC+knOJU4/m1DvWPaqQ+2XO/dOd904cnhA71id0Y3/4dn86qu+nH/vtHUe379sLC6EE9A== +1438613947.950,1.000,0.000,HISTggAAAMZ42u2XXQ7CIBCE2aW01Zh4Ae/i1TTxBl7UI/ggvEzyBZpG48POy2RhdvjpQujl8byndL6lD3Jlq+zXVwoEAoFAIBAI7If9yI/afaOvCxv0t/fjBONliE1i8tF20pXKq8SNZ+GWv4Bv8zlAnkO/chbd2pnPUdoXyW9+J5gv6WfYnyyxi95gf7yzf1Pn+6vOwN8H69JhHF1XgXGpHnOn/jWmulL/BHkJ1lPgfBv9xw3Om87/qH7rvWM770P/0v1qf6azNx7YBVc= +1438613948.950,1.000,0.001,HISTggAAANF42u1XSQ7CMAz0kqRlkfgAf+nbkPhBP9ojRw40l5FGCaDSA56LlcQeT1Jn6fU+30QuD3nBV6urtWmRQCAQCAQCgT2hfzo/7fSzzjz2YbxBfmYTviPJeH1vFvDL0Dbwx3blGyEe+w3yYf5MeKodGvnOMH4iegrw1fEj9A/Ac+jUV8BvJPpb65eBD20CXQ5xTvIkooN93wz141CXyCeknlh9Mh1O/ouc5ElkPziJ0y/3lZJ1Y/tGSDzCNz6PrcGnb557e90XulG8/ngeVN8TvFEF6g== +1438613949.950,1.000,0.001,HISTggAAAOJ42u2Xyw3CMBBE7fWfFMCFXqgNiQ5oinIogQMJh5FGm0ggFDHvsop3Z73+yjldb5cQjvfwIs02ztbOjyCEEEIIsYb4Y/3exvvt+Xu/5+A7k/hM4hF8LzJrRG+QB62BXeoqs52Iv0J7g7wd4jvkXfSD1NVAP0A/oL4C/VRoPxDdAH+DcVWwDWyFuieSrxB9I/OC65bJvupkPTtZ30TW06COTPxs/ydnvyenvRG9kXNSnHElJ19y+mHn3YuPzvmLK+8f5s8b4+PGe2sr6UP36Np5sbAPouoRQggh/ue//AkeOgXX +1438613950.950,1.000,0.000,HISTggAAAMp42u1WyQ3DMAyTLB9B++gC3aVrdJ0A3aCLdoQ+6nwIEAzyClDxQxiiRFmJndxf79Xs9rQfYrJPLo+PJRKJRCKROAc8R3DqufvBfCc6ti4ijjqD/zxk5RekbhF1t3ibXIV+qz9Ivov8TvIa1F0gD3Wd9L3xdfIF/BaiR98G6wG+leyng36AvkF/FXzY/JQO5xewNrLvDuziORXIZ+91CN8gcwkxhyLOEeszxLlh56cSP9/ZjxFdITp1/8TBe2pv3Ejf//bd8y8+nAUw +1438613951.950,1.000,0.000,HISTggAAANB42u1XywnDMAy1JFtOWugE3aWzFbpBl+sYGSGH2pcHDzvkUCh6FyErT7/IIrm/3s+Ubp/0hTUpTepjS4FAIBAI/DskWhD4wZzJyfkUIhEKUgb5MD/9OzFPxlfCw/MCdm9yIXlnsHf+CnrnXcBvgfg+sBvJq4Lu5LkF8u/+r5Af1usQfyV+se5CeBX8Yj8r8J3oCn4zyVvJe8X4Rv5DmF3JnCFPyfxi3excibTB/Avh5cG9UlJ3OnhvZXJv2OR+Yv2Ug/tqds+MoGf38A4juQWz +1438613952.950,1.000,0.000,HISTggAAAL942u2XPQ7CMAyFYzcNaRm4AAM36dmQuAEX5QgMhOWJT4lQBYvfYsV+/knqWOn5dr+mdLqkF6YmrUnfHikQCAQCgUDgEyyO4CfnZoP+3llTXAdp8j58I3/pP4v/LHblkTTQH2S9gl15VfKvwqvA130cmyyQR+tYIH6GfAXqrRBf/Qp8J83rHV4R/iL1ZOi/0umbGex5sM8c+nMajOP0P9SJQ3qDNd2/0XtP0kG/95yyneZaz987+//3/LYn5vIE+Q== +1438613953.950,1.000,0.001,HISTggAAAM942u2WyQ0CMQxFnWUyCxcKoAg6mNqQ6IBGKYEDMwg96cs5oUHyv1j58ao4Ti73x83sfLU3yibTJvP6tEAgEAgEAoHv/0HUf8w42bFPYr3bVfwHqbfzg+Ab/DXoVcgBUvEFfhfBN/C7vxFyEnmPsDthf4Geyn+CbMKO9VJvxv4MvQl+yPNcZ6yzyEedp/KXRH8xjom4SfDF8Ud99n9z8jCnP4tzr6pTv+KzyCcJvnbeX+vMp3f+ZMc+d86d3rl1tHcn/djuX9+tT7+9ADyUBQk= +1438613954.950,1.000,0.000,HISTggAAAMx42u1XSw5CIQzsB+pv4QW8i2d7iQv3XtQjuPCxmWRSQzQa09k0lKE/KITT5baIHK/yhK9SV2nnuxQKhUKhUPhvaJXgK/XTSb1N2jeiV5Ad+I2sc5gXMkbesLsl8078jnV7GGOcQwbxswH9DvJuwAuQnfhh+iD5DP4B4gjIswE/SB6e8FHfSd3Qbif1c6i/k3oE2X8h++jkvDpIIefGEj/432mkD5TY1yQudh6zuFh/e9Lf+uI9pEn/v5vH4rHJe/TT79SvvoP6AAXXBVc= +1438613955.950,1.000,0.001,HISTggAAAMN42u1WSQ7CMAy0sxa4cOTAX/o2JH7AR/uEHkgug0aOEFWL5LlYie2JEy/t/fl6iFxv8kZsUpsM8yIOh8PhcDgce0B39j/qPcOXfnHQXw37j/9FwpdBz3iQD/36OsG6NFlBn8Af9QX0k8F7hv1C+CvwdbsTOU+Mc7rdBeLLxC7DO07k3RLJL8tHBbtE8lMIP0q8RyTxpMF9lMWoVyX1Hoy6D4SH9cNoX1j9ZMVjzZG48Rzaat7pj+I86vfpX+PUFbIgBNo= +1438613956.950,1.000,0.001,HISTggAAAMx42u2WSw4CIRBEm4HhEzcewLt4NhNv4EU9gguZTSUvkOjoLLo2FaD6NzQwl/vjZnau9kbsHDov16c5HA6Hw+FwHBHh4PHDznF0foFxAH2YzDOCPgkvELfJeAW9cgLe7PX/Ve0r2Ot8Ey6ds+gK+Mlg30BfwV+R+nSs3znDuuaVod4E8bb1k8QrwgZ50v5p/2gdEfS6r3GyD1fQx0H/R+grG+QzqpPyV/90nmxwjskvxdF6bXB/2Ie6ve7V8OX4v7rH//1+Tef9Agm7BPU= +1438613957.950,1.000,0.001,HISTggAAAMx42u1V2w3CMAx0bKekERILsAuzVWIDlmEsRuCD9uekk0MlhBC+H8vN5S7Owz1fb4vI6S4v2BrLGvXykEQikUgkEolfQPmTusrgd4xK8sjHBnXQr8L4BHkl8zeeQ16JTwPeBOtuML+CvhM9B/4Mup3wNv8j5A3iAfSQZ2TdM+F3Un8HPye6BrxK9tWD8/DAT0l9Qs5FyTkZRCV8J/csuteV+HrwvoyM2+D6ovcoRM9IHaN67/YfRNQXPuVbdvbTMjhfv/Rf2r1vT0veBao= +1438613958.950,1.000,0.001,HISTggAAAMx42u1W2w3DIAzE2ED6iJQBsktnq9QNumhH6EfJz0knJ81PWvl+TsbmsImBzI/nPaVpSh9oZ+mcb68UCAQCgUDgGJDYgr/+nrJxXv5SNzvjBWwlNs5roLvEGcShDsYV4LFzJfEG6187D0R3sS+gN5L1T6CP65yBB6euBnYFNpg/gL+R/HAflNiV6CEb6YcK9SrkoaQOIzqsHwrp1+yMm3NeMvGzuEbyLCvr9PIWZ9w73+rYXr2y8V5Zm9fee/HX3zvZ6T/a/4G8AZOxBSQ= +1438613959.950,1.000,0.001,HISTggAAANB42u2Wyw3CMBBE92OCSUQHHOiE2pDogEYojRI4EDiMNLJ94BA077Kydj07m0SrnG73q9n5YW9yjb7GuDxNCCGEEEL8Dh/M+6DO97+ukc9BPRaDnAP6oJ8CcQcxyBnv1YZOQn4i9aVRN0P9Aucj+Kmg86k/QH4hPiaYeyb6tXPefWPuIM81G+8loU+S76KQegc9JzrsO0syF/o0Uuekb5J7bC7mx0lfI36xL+vnnXvDB/cS2xvRuRe2vo99Y37lUwghhBBCCCGEEP+KvwB1hAWn +1438613960.950,1.000,0.001,HISTggAAANN42u1VWw4CIQwsLSC+4h7Au3g2E2/gRT2CP/AzyaQYdxMTOz+T0tIO0O1eH8+7yCId1jl11ttLAoFAIBAIBP4JaaP9idiz69/qUCf/8GcSz2zcb0RH6VyhTgZ/gXwK6yNfg3ojz77zjpwHdRSIN7Dr5L4z2I3oGOsXovfU+QB8hPhCdDZyr+hXcn8KeZXUMWDUhX7sHyPvjO8kTv8pOQ/WqU5/Y99m0o/JOZc4us357tWZB7P11JkP5swNdeLWmpe/Ose3/h+s5f+0/mw+fQO3AgT7 +1438613961.950,1.000,0.001,HISTggAAANJ42u2Wyw3CQAxE1/tLQg40QC/UFokOaIpyKIEDm8uTRkYKEhw8l5Hj2Vln145yud23lM6P9EYZbIPz9ZkCgUAgEAgEAr9Hxn+aIW/Q7egiT58ifHdUrM94XsDMd8T0K9BloZsGz+I9WM+MeM+vg0/wbYMXcBf6RezToOvQN/ix7glcsW5F3MQ5VdS3iHvg+dO3Ozp1n+wH9kH6sI9Yj4m81w/s8yb2S0JfRVzFPZgzn9mZzyLm3IRPEfNrjo85eg/2pe/YUdhBnR30/TfYCxrJBcc= +1438613962.950,1.000,0.000,HISTggAAAMh42u1UOQ4CMQz0kWQPhPgAf+FtSBT0fJQnUJBtRhrFK1EA8jQj2/GZxOfb4ypyussb3lk72+UpiUQikUgkEr8E/fI8GvS3nfmUsAX9HM4XEqeQeoywDmTMW4GZXwV9g3hHqHcFv02eO09gL0RfId/mv0B+tM+gX0FuEGchfR1Aj3Ms4I99YLwJWIndyRwwL8o2uE8n9+mkT/buCnlP2K8H/0Ub1K2kTw3+B4F6EHUQT4LxJWjfu9/YfrIP70eT/4QG560vPrMFaw== +1438613963.950,1.000,0.001,HISTggAAANN42u1X2w3CMAyMHylVKwQDsEtXYgUkNmBRRuCD9ufQKWmKEJV8P1au9sVxEke93B+3lM7X9IbNVmar0zMFAoFAIBDYF+TP9QJ10JX74hv3zQq6meSH1sAqieuAP0AczivAo76Dbgaefcf4EfQX/wH4Jf4E+Q8wzkSnA78j+PeQT0/4kazPQd8Jr4Q3Um8p1NUqzwXWF89JJvGJ+HlBB/P/+O8pzI/rFWKVjK1QD3YPhdxLIfFa2S+ksd/Lyv7U+n7Il/1a/fXH7+Be59mqJy/JggVc +1438613964.950,1.000,0.000,HISTggAAAMx42u2WzQnDMAyFrcR2nFDoAh2gW2S2Qjfooh2hhzpQHnzYKTSHoncR+vOTlVjocn/cQjhfwxtjlVblsD6Dw+FwOBwOx+d+8C88vbx28P1sp677G8WrfdC9D/xkj2DPUF+GepLERZGTxCXgG8EfRd/iFuDNIhepYwbeLf9UZZG4JPokeQXur/eZwV+gHu2/2ksjjuov0FcDHvoOCfqp/18CP/EZnNOSEXj0XOJp5Y+d5/W+U31ne+eMHTTHBuC1H83ZvfP76z68AFkHBP8= +1438613965.950,1.000,0.001,HISTggAAAMd42u1WSQ7CMAyM7SylggN3/sLbkPgBH+0TenEuI40MAiQkPJdR7WS8NHF7uT9upZyLw5zFWa9bSSQSiUQikUh8HxLY5UP6CjzR2P8g0avgN5KvgL8Cd+cD6HdYt4BOg3XzeSXrK8RB/wpsoI98dD6BfSH1YZ6TB+wbRA/tg9SF/VTSrw7+SvJSko+R/Ub0lZwrI++JnT8h581IPIzDYCTPFuSjQT2MJbif0X2K5ocE8ezN+SJPzq9X592v6OV36M/7sAOWgQTu +1438613966.950,1.000,0.001,HISTggAAAMF42u2WMQ7CMAxF7cRtWiYOwF04GxIDOxflCAyU5UlfXoLE4L98Nfb3d9vE7eX+vJmdH/ZBP9gPbteXFQqFQqFQKBTmwSfrHf9xTcQ9yfek/gJdT9ZZpyH/iwCz/wUc8BkiTt6gW8GbWCcH/E5JHfa5Q8+69BuoO5K+esI7eCAewn9Jnh/zGlj1E2IfZvu7Qc88E/7qHKjrED5N+KyiD0v8utj36r6zcz97/iidem89qeOT5uG/zOX6fv3I7w2RWAVk +1438613967.950,1.000,0.000,HISTggAAAMR42u2WSw7CMAxEnV/blM8JeheuBhI34KIcgQURiyeNEmCDhGczcmrPJHESdbveLmbHsz2RGofG8XQ3h8PhcDj+FcG3wPv5A36jOknUqfo4mGfqP7HDGfHUuCBOIn9BPnUz6g/Qpd4e+RU+c+MV44uY/w56K7giP4t4gk8d/D5jfQnjGXFBXkF/mZ/gP4nxIs5HEOdF9TuKc5mFHn1VfezoxE5M7u0f+6vup3V8wofvROrcdxPrfddHzTN8+c69+AFJTAVX +1438613968.950,1.000,0.001,HISTggAAAMd42u1WSQ7CMAz01jYIIfEA/sKVbyHxAz7KEzjQXEYauQrLAXkuozpje5xUaU+3+1XkeJEXfGVd2c4PKRQKhUKhUChshw6uY9ze7NfjMTiHsf9D0qfzBLoA7vUa6AzyUYd1sP4M8R3kL+DTQRfQfwZe4LkRf1n8QOIBfrpuT3w1sq6kLs7jZL/ZOu7PRM7VoA47L/ZeBey3J3olLMRXkHki6cd8GmFN8o340yTO6nsyf6bbep/oYP63dZ++t3/V9++/h0+baQVA +1438613969.950,1.000,0.001,HISTggAAAM542u1WOQ7DMAzT0cRJs3Ts0L/0bQX6g340T8jQeGFBKEbjTVwEygYl00fyeH9eIre7fOF71D3ac5VEIpFIJBKJf6BpQRcftdFvg/ELmW8wznR+/hsh76BjRL/GQvqsOgPRrXwCPpKI9WfSB/KFjM8QC/CF9IvzHDj2PRF/r6BXiA8WcCd8IOt1EoXsrwV9WKCjxEclfSg5N1F+DO5FIT5pcH41uEds/dZ4jzx4L1jeSF0hdY++79r5HTy7ngZ+HYV1/l5a4/yz9kE3Xp8FEg== +1438613970.950,1.000,0.001,HISTggAAANR42u1XsQ0CMQy0Eyf5QMEC7MJsSGzAYozCCBTkm4OT800qX2Ml9t/ZkW3pr4/nXeTyki/ysDpsur0lEAgEAoFA4B806lnKizzJiVOIw3MiPOx7I3wsPsO5gM7ubxDXCF8lvI3cV9BtxF/BX+B+r/s07HnYTvjRGsmnE90N6kH+DfJg9RXQRz70d+d98D2wPtQx0rdG8sT3Yf1lTj+hDpsXc/o0k7lI5L8pk/oz4WXzrI714uXgnMqkrjr7ZnYfze7F2fqP7s9Ve57llxbp//g/SkEFvw== +1438613971.950,1.000,0.001,HISTggAAAM142u1VWw4CIQzsg0VcE+MBvMtex2uYeAMv6hH8EH4mmbDrSuJH52dSSodSoFwfz7vI5SYfeGWtbMtLAoFAIBAIBAIcutO/VV935qUdNmCMcxLnxJ4qJ9A9VM6wDo4r2EZ0G8+wvoGNeRTQz6Df+AT6Le4MdoH5iegX4i9Qhxn4SPY/QTzWmdUddZzoZoj3TlwifnZP8FyUjPvK82X3O3Xuu65k9g6E6ObOexKy3615KXm/3/YPNt9+3IdG9+FR/fhf8hqtN+z/fAMZVgVQ +1438613972.950,1.000,0.001,HISTggAAANB42u1WyQ0DIQzENixL8kkB6SVtpJ1I6SCNpoQ8Ap+RRmaVQ/vwfEbGgw2LMXu+P24pna7pDessnfXyTIFAIBAIBAIBDvmy3vPrh+vA+UZ0Bnol/4vDzoSNcOm8kDgK/gx2AVZHN+IfwF5gfIV5FeJXMj7iNLKeDPFRX8l+jk6+DP5GdAX8jeyzkPNOEG8l57yQc8xQN5nobLJulNShkXo3J69svH/i1Ks6eSrJq8RW576Ksx8hepZ/tj/JJG/td/LjPryX9+Bf+XbzfV61dAVE +1438613973.950,1.000,0.001,HISTggAAANB42u2U3Q3CMAyEHeenFITEAOzCbEhswFKMwwg8kL6c9CkVqPDiezk1Ts7nOPX5dr+anR72Ru6cOvvlaYFAIBAIBAKB/yMJj/Yt8IGOw3oWdog7xHedK+xrcM5Bf9ErnWf51nOT5J/AVxE/s3xPwlni6mvRPXY+SFx9qF4VH1Xup0Aduq+J/h7qbdDvBqz+HeqvolcH76mufB/07rT/Bv030DG4P4M8JnUnyEv+MvQ1DfLR/2IfzgeaAyM9WveV82frOfkr/a3zBb7s1wtlfwWx +1438613974.950,1.000,0.001,HISTggAAAMx42u1WOQ4CMQyMncPL0dDzl30bEj+g4Zk8gYJNM9IoREgripnGyuHx2HK8e70/bildnumDvFnbrK+vJAiCIAiCsAdMuqfi2eQ+wuG8wP+gw7qQOJn4G+HpNojfQni7rXB+BL4GNsDPCU9A/EZ04b0F+CroOsO6gY6Ae92e4P6B5DXKN0g+jdQ5SH84qV8h9cmkvxrpM9Z/Dn3LeG3gj+fsPTBdLD7jNWLr5Ls0sj/SP+JLk/Pi2znnP85LG+jdez7ruyTdf1WfN18xBZQ= +1438613975.950,1.000,0.000,HISTggAAAMt42u2WMQ7CMAxFkzhtUlSJlYG7cDYkbsBFOQID6fKkLwNbwX+x7Njf1pdr9Xy7X1M6ntILNmwetlweKRAIBAKBwG8j74z333UugjeLPoyzvuI/8N35yTeBp4CffoafwFPAR56tfhl2hs9+B7wb+m15fdiGugY7w3bwN/ThnBX+KvRckL/Cr0Iv6sB5utA7C/4mdDOHZ4Kv8qvznZjYW7X/ag9N7K85epJXxZOIV0cH+/COmHMfvLmyo6+6G+XLu1acO7KbO/4EBWME6g== +1438613976.950,1.000,0.001,HISTggAAANN42u1W2w3CMAy0nRfliwHYhdmQugFLMQ4j8EGR0KGT3R9aJN+PpYt8Oad2mvN8u4qc7vJCWaIu0S4PSSQSiUQikfiEbqynf1bf2v0s6EODvgvoevh6D5KowbwKfAfegFei14F/5zeIA9YnWK+g00D/ALGCLsZG+CPsP0CX7T9B/iD+e5DHfCPny76Pd94V+qA7/VGdfjLi1+vDSuaB9aUSvjhziXWyOSvEhzjzpSv9G5k3CdYtRCcKpqvOPaVBn2vvvV/f33v5P21d927fK0/Z1gWX +1438613977.950,1.000,0.001,HISTggAAANl42u2WwQ3CMAxFY6dtqgYQA3QXZkNiAxZkBEbgQMrhSZYLAiSE/8WKXfn/2JHr+XQ+prS/pDtys9KsHq4pEAgEAoFA4BOQP73PY89y4rLSb/Er9jvyLf5hpQ7q9uIdzn2zBfwd4gPi1EPdBXm4zzJ/b+TJhl7GNzhPzVbwjdBX4F/sFvkW/w78E+LV0DEa9alG3flO2CfWrRp1zE4fxOC1dCTn3fI76lSHl/nV0OG9++zkE0O3OnWyoI4edeZMduaROHV6ds7Jl+bqq8jxf/yJPr39vjfrRwXb +1438613978.950,1.000,0.000,HISTggAAANN42u1WwQ3CMAy06yZpAYkF2IXZkNiABRiREXiQCOmkkxMU8fJ9rLixr7nabi73x03k/JQPrFqtdrm+JBAIBAKBQCDwhf64T/Ge5eRTYqVzLXC/6z3H4tgV8tqgP0O+TPa35wn8K8S1dal2g3WG86L/AHE78J5gf3F4MQ/qsRE90Db+I/AURw/UZQdejGP6msObCD/qh98Rz4f1baRPjNShdvaXkjxK6pzFJVLHLL859Y1+Gex7I33OdJTO+SOD78N4dNIc1cnzefZ8//t/6A3/xwWx +1438613979.950,1.000,0.001,HISTggAAANR42u1WQQ7CIBBkWUpp4s2rf/FtJv2BF5/pEzyIl0nGNbW1B2cuE2CWhYVlOc3XS0rHW3rCO1vnfL4nQRAEQRD2gSkEf3nOtvAeWMCoy8BGxvF/6EQXzfuyG4i+gA7bqEMegEewa50r6Ar0T6Sf2TWwG6Ef11VBF+kb8AHiN8G8hey3Qjyd+BvIOeJ6ncSZxdVJHCzw66RdiD3Lm+geZbIOnK98mFcsjzzwn4gexzM5D6bb6v2xL+uXbfQOrlVnbWV/S+vDr/8DttN+BUF4k5cP+eEFjA== +1438613980.950,1.000,0.001,HISTggAAAN942u1WOQ4CMQyMj3iPjp6/8DYkCno+yhMoWJqRRg4L2srTWEmcsePYTs63x7W10729YZuUTerl2QqFQqFQKBT+AakQ/BSHbJ/+yCdEIq+TdeQzYteJXeQN4Amw52TssK8D70TGQfQDeDvIIPMO/OhfkPEE50L+dZMz4e3kXB+5ED/R3078XyA+K6zPyf0YyR8jekH0MT4zmRfgU5J/SvLTB/0Qkj+aSE/qFO/VSBw9qeuW1LuSeRusd2bHkn4iCd+oPdnZB0f7pOzsz7qT7+j3SL/Ur3f9mP+OvABzTAVs +1438613981.950,1.000,0.000,HISTggAAAMZ42u1XSQ7CMAz0EtIGkPgAf+FtSBy496M8gQPJZaTBQeJSyXMZNZ54FDtN2utju4tcnvKBd9bOdntJIpFIJBKJfUCzBLvoj07G/6WL/G0yjnkKmV/xexL0TriQvAOt8wHY4dmIzoge/RvhAusrwfyhWzqv4Ntg/Aj6BfQnyFshPvRn4sfqUsl/AFufkz5X8LGg3xgXsl/YvmN91WBfG2GdHEd/VifmK0Gd2HuJeZ3kkaAOCJ88R2yyrr/C8h77fs6/Af9oBV8= +1438613982.950,1.000,0.001,HISTggAAANR42u1Wyw3CMAyNY7sNcGEAdmGNroPEBizKCBwIHJ705KYqF+R3sfyLX9w4zeX+uJVyXsob2qV0Wa/PkkgkEolEIpH4PWSlX8i7TTbW+b77Bnkp6JjvxF8h34jfIW6C9Wbwz7CeQZ5DXCVxyGMK6n301uWhyyOxN/Ab9HkGeSL8HOwG/UHeTHfgx/rm4G9gb9AP9h2NSCX9wH0ZOfdCzlN0zoTEaTB/TtYzwpvNWTTXSvKF9JvFsXnWYM5l430S6aP1ZJDXv/4H1t7be9XZfX8vJ/EFMQ== +1438613983.950,1.000,0.001,HISTggAAANN42u1XQQ4CIQxkigWMMX7Av/g2E3/g53yGT/AgXiaZgBvXvXQuE9jSzpYWwvl2v6Z0eqQ3cmd0tsszBQKBQCAQCKwJ/NgeG/8HNvaDST9L5/l7JrbOO7IzWp+FHWie7fY0bsQfe+9cOlcas15e57TOKX4Teg5Cb6F5Hlfhz0W8SrqLyMdR5LOJ/XORvyy4Cr+836yvkJ2qDxN1B/F+yaJubLLulB5M9oPyY4M+w4BtUp+JvnPRt+lLXUnkHZP7uPa5jYXnKla+l/51fwzjvACmLwW1 +1438613984.950,1.000,0.001,HISTggAAANZ42u1WSQ7CMAyMHaclLIIjB/7C25D4AR/lCRxILiONnKocQHguoySd2OO6SS/3xy2l0zm9kRtLY70+UyAQCAQCgcAvQf7MjwzOi/O8AjOdLtwnA6N+alwaG8yjbgJW8h/b12fIr48r6Hv8A+grrPfxDua3sD/muYc8jcTZAB8hHtZzJvtWkv8EPpXko8R3AT9YbyP1N2CBMb5Hrx+M1COTvjTSn6yvM+l/GdSp4y+TfJR8T8WJv/bc8Hx6eh3UjcLzNepPPnyurj3HZaHfb7sn5QW+QwUm +1438613985.950,1.000,0.001,HISTggAAANp42u1WMQ7CMAyMbRJSEAM7f+nbkPgBH+UJLMly0smhUmgH32K5ObtnN3H6eL2fKd1TgzUrzer6SYFAIBAIBAJHhPw5bm/oYD0KVpw4tOqsM12ejo6FxJ3guUFcXz8Dr4Df82fgL5CngG8kP65X4BV4X4X4K+SpoKdCngvEZbAF4jr/5ugz8IvDw35n4meoF58byWukz+gzvpLvpQ7fBvc97i9x9iXqEVK3EF06eI6F9CWRukfPvTnzhp1z+XGuysZ5PGtui9OvrXpm3zOz+73X/XyY/4svlSEFBg== +1438613986.950,1.000,0.001,HISTggAAANp42u1WwQ3CMAx0HDcpRagLsAuzIbEBCzESI/Ag+Zx0ciIqXr6PFVu+cx3XyvXxvIvsL/kiN5ua1dtbAoFAIBAIBALHIZEzsyyvQx09ndRhdSm8FzPhx3oM4kb4kCeT89rs0myBuEG82wrxCvGucwLeAnkb5Bnk9fMF/Ni3hfh3otP5zlDXCjoV/BX4N/An4FtIH4z0r5B7zuQeC9y3OXNQSb+zM3dK5lmd/4LNpQ3OP9NLpE/q6Hh9Fef7bbA/Mrl38uA+G92HaXJv/rp3Z6HyH6SD6k8fIaEFvg== +1438613987.950,1.000,0.001,HISTggAAANJ42u2Wyw0CMQxEE+ezG04UQC/UhkQHNEJplMCBcHnSKFGEEBKey2jtseNNvPGerrdLCMd7eCF1jp3t/AgOh8PhcDh+C9Hf76/POU7u02y8Tebnf2KGPYi81JnQUV+g4/oVdsZxva3zAf4G/5t3cBH2hnoKuIr4DH+FP6HeivVYdxN5sojfhD9DV4S+YH936JLIb+K9ef4KSeQ3waN+NtFnqi/jID/PXfmj6Nck6skD/ex3r55tsB/qvkiL91ScrGvVvjpfPj1/fJ59aV4+AaSrBbs= +1438613988.950,1.000,0.000,HISTggAAAMp42u2UwQ3CMAxFnTpJKRy6ALswWyUGQGJRRuBAc/nSU1IhIQ7+FyuJ/7djJ77en5vZ+rAPfLdpt9PtZYFAIBAIBAKB3yPBOh3kTR099VN/4mfQI50K52qb7ix8F5vFuvAq+LU4J/Fr6wJ5zLK/dvhF9i+yr3kuEE/vRfp0r0X8K9TnDHXWPB36miG/Cfqgda7wbrT/FfrpEM9Bx4BvwDeoT+m8Z/qHmpfWJ3X+vXfi+cE5kgbj2uB8GJ1PBnU6yvs2rv27/hvphwV/ +1438613989.950,1.000,0.018,HISTggAAAXd42u3a203DMBQA0Fw3fcEK7MJoCIkN2IApGIsN4KOtkK5kOUnTUNpzfqzGdn3jx1XU9Ont/bXrPj67g9WxjEPx8v381QEAsLzj81hXRraPge2yMrI+KnFGI65VKksqN436kupP1qksle/p0zgl9evT531qv03ttpX4T/136fO20W5TGe9UPqT6XYrzVP+Y4tynOPeVeesr87BK4z2m65vUL6/7prL+tXWOyvV1ZZ/lc9JXvmfV2EelcQ6jsR+jMk4M7F8a5yUa56IfmB/KwPywHphn4sw8N/Z6TBzn3DjjzPuLkXk+FpqXbub1KzPNz6Xjn2ucmOl6d+H2ZaH1X2p9pu6fmDneMnGcVl4+93wtPQ9j+5eF9v1c+T4utB//Or91M5+ja433XuJYKr640Xn6r78X3Ou+to7uDwB5GpAHAADPHwCA5wEAAAAAAG6D348BAAAAAAAAgEvxvwQAAAAAAIBpvGcBAAAAAACAX9f+/ix+ACPOB9I= +1438613990.950,1.000,0.002,HISTggAAARN42u2ZQW6DMBBFmbEJoZV6gt6ki56tUm+Qi/YIXRRvvvRlJ1RKFN7bjAb4wx8bbCTevy9f0/TxM/1RthhbzM92BgAAAOBYxEH6zH/WhYlOXySPTj7L8WJiu66Kvpr6LS6delpX86Z/MT5PWzyLXvOT5OsW3+Q+q8RJ9IsZx6Uzrqv4WgZ9FhmPFl8lLyafO/Os4zyb+a1XPifu+U2jC5OHOZ/mPtnRaf1q3qM079uoL+1TqYN9FDMfbp0oHf+3rlc5eDwG8xz0mTv9j+47caOuV+/R9r1r+3R95M5+Y6fP2Nk330P37fuo/fNdf5/68SQ+WIcAAFhH8Q8AAAAAAAAAAAAAAAAA8LzwH/dB5uEX/B0GDA== +1438613991.950,1.000,0.001,HISTggAAAMt42u1X2wnDMAyUrDzUUEoH6C6drdANumhGyEftn4PDIph+FN3PEck6yZJjksf78xK5u3xhlbVyee6SSCQSiUTiP6BZf2Jg30vnWYldyHenAbe4CeJm8Bfin8C/gn3u+Avh5l+AmT7ab5Uv4N9AbwPG/C3eIc5JvENdrc9Xss4h7wLxqKfATvqH/x04B4O+4HyU5F+DukbsbN49Pen0gZ1nI+uVvD9GzpcEdRhLZ/8SzFNO3iOsXxLUjeqPuv90sP6v9nG2Dj0AkVgFAQ== +1438613992.950,1.000,0.001,HISTggAAAM142u1WSQ7CMAz00qYQBBL3/oW3IfEDPsoTuCSXkUYOCCRUPBfL8TZZ7Ha93a8iZ2nwJrVJuzwkkUgkEolEYsvQP+Fjb/IwEq/B+qjE/BP5L3WQE9i7PhP/hfgV0DF+Bn7dfw/5FpK3EN7dbweyED/07/YT2A9NVqh7hLgK+SrZH/LD8zSwKznHEtwju3/cvw6+FyH1WF0HHd+VBHEerDNebF8e9IkS3Qf7i/WjBP1sg/7y4ryQQT7RPNOg3rfntH4oz69/p7YOewLePgTy +1438613993.950,1.000,0.000,HISTggAAAL142u1WSw5CIQzsBwSfMW5deBfPZuIN3kXfEVyIm0kmxefOdDaTtrTTAiHcnutD5HKVN3ywDrb7JolEIpFIJBKJ/4FOrrPA72BrwEbqYrx8WUchD/tqEC9Epw9eBh+Aj5DXQa/Auk9eBfsEdiN8hnzU6xDHfPzXO9F1sHHuhcxXCeP+ot+I7ux5GtRVcp+Q6+R9skBfia6T/i3w4/550E8hcSe2knvQgvklmNeC+jJ5Lnvfr1/r7H4/X4TXBPU= +1438613994.950,1.000,0.001,HISTggAAAMx42u1XOQ7DMAyT5CNOG6Af6F/ytgL9QT/asWOHxgsBwrG9dBAXQhElyohgwPfn6yFy+8gP4WA92Pa3OBwOh8PhcDj+Fzqo184+Z/UGjH6R6CPwQuoDyQfgikzyEfwTfK/xhdRVvoI+gW+tX0GfyXmrTwH9CrwRP3aOhfhkotsgxnyGeQr4hpNzFYhxHzLJF4ilsQc4lzX22sj7KLJ3E+kvxC+Q/U6kXohPbPhZox+bA/+Ddt4L2qnTyftsFDap08F5Zueerad9v3TKBfM= +1438613995.950,1.000,0.001,HISTggAAAMp42u1WSQ7CMAz00kChiAPnHvgJb0PqD/pRnsCB5jLSyEEUVZU8F8tL7HGTOB2n+Slyu8sHvkhdpD1ekkgkEolEIrEnaPJbBUb4Mh3/IxXyoHSQWLcjOsY78SOf6i+EtxPe1X4EyewF9J7U78i6Kg/EfyF9s/on6OtK4vtGfgPYz6Ab4TGQ/qxx31r338g6Jee6BPU9OMfROZcgL+rly7oa9KcBLwvuvwT3vXWuRfm0cV7qn+ao/hivK8dv9c7s5X3Kvjb+nm8gdwT6 +1438613996.950,1.000,0.001,HISTggAAANB42u1Wyw3CMAyNP0kbcYAB2IXZKrEBCzAiI3Ag9PCkJ4v2gBB+l6fE3ya20/P1tpRyupcXbLAM1sujJBKJRCKRSCS2Q34k7vr/9+E+ypXIJZAbsIP8va6DJ9BX4k/B/gj2Dvo14HlwA/sJ5B3kHfRY/A55H0hcxg5rIXl0kq+SeBOcgxNuwXfOEKeR+qqwb+RejdSfknvF81DSL5GfQurVA/8G9h70Tw3ylCBfCfqd9WHU94WcVzR3LMhfds63rfayU/9bc/7f3r+1Xp+B2wWR +1438613997.950,1.000,0.001,HISTggAAAMx42u1WSQ7CMAy04yRtAwcewF/6NiR+wEd5Apf0MtLIAZQDwnMZOd7GaZr2en/cRC7SYZ21c9qfEggEAoFAIPAL0D+dJ03eFyV10mB9Lx/jMvyXHnEV7Az5GbgSzlBn6bxC3Ep0LcAN9G7A2K9A/UL6NvAffAYuoIfZXjyun2Cu5sy1wX4V8BvpU2FdyPM10tfIuRESj+eW2TZom+MXp7468yCrk8fez0J0Ksln94c6cWnw/VfSP315T6YP9c/6vuibfp2sZ1j3CwqcBP0= +1438613998.950,1.000,0.001,HISTggAAANZ42u1X2w3CMAy0nVdTQGIAdmE2JDZgQUZgBD5Ifk46JRIIKuT7OcWpz3bt9HG63i4ix7u8EBprYzs/xOFwOBwOh8PxPYQf6SlhGdi7vgGzfZaPkTiB6MXGCfwzXFdgncBu5Du4QJyuu4K9Nl5IvRl4Af0K+xW453sA/x5/13hP6qwQbyV5JOKXoZ44qDORtZH7mYgd56ngfwpZYz9xXiL4JdJ/nHPMdxTXyBzbpI5CvkL6a4PzY5PnldU9qzt6brDz/inon70H9M3rdGN5b60/+gRZHwXb +1438613999.950,1.000,0.001,HISTggAAANp42u1Wyw3CMAyN4zROUQ8MwABs0dmQ2IBFGYEDzeWhJ1NKDyC/i5X48+ynKMnperukdDynJ3Sxstg831MgEAgEAoHAL0D+fC7ZWSdxeAX+i8nx93UB2/cHiFfSh7ffoJ6BVfDX/s8Fa+BvkG8kTmGNc1bgRz06/wh5I+QdFjuReQz6rlDHgI/p30jfRvQoEDc58yvRg+mXnXNTyTlEfZWcn+TwZVJHSbyS/EL8gzN3IX0Wwo/IZK1EN+bXlfeS14+svH+28sqbcXvfv996zz6dZyvPi/8BFSwFEA== +1438614000.950,1.000,0.000,HISTggAAAMd42u1WwQ3CMAyMHadJK8EE7MJsldiA5RiDEXiQ8DjJuhR4+j6n2hc7PSVWLrf7ntL5kd7InaWzXp8pEAgEAoFA4J+QsGDKHyF+yUFfldRR0p/1U+c9iTzyFeLWeYH80DfnP3B9Bd2oV4Crk8e4OvVx3QI6c/o20K8QH7pT543svzn+meP3SvxZybfHG/TF81Agjv2zozfnfCrh4uht8pwWojNyD9JkX3P2L2QusHutZC4omRf5x3kyO5fk4Fz8Vvfx7wXviQWy +1438614001.950,1.000,0.000,HISTggAAAM942u1V2wnDMBC7s89OEyhZoLt0paxQ6AZdtCP0o/ZHBeJiMPTn9CPic06K/Mjt+XqI7Id8kRtr43R/SyAQCAQCgcAMaETwk4OezGk0t3Ty/UT8JOBM6gvU+3iFcSN17GOkjn0y8Ar1zhfwXUjfFXQLGe8+t8Y7mYd6C/jpz1fwUcl3GrxXSV4byVmIPsu3kjxxnQvxZ876m7NP1cklkXlsvyrRY+cB/RenjxI2cp7EyTU755P59uYr0R+9Z7KjP+u+/9f/YvR+nqb7AVaRBU8= +1438614002.950,1.000,0.001,HISTggAAAMt42u1W2w3CMAyMnTQpL3UBdmE2pG7AEozHCHyQ/Bw62SAhIfD9nBr5Feec9Lhezikt1/RA7iyd9XRLgUAgEAgEAt8A+dN6xRlXCL8bn8V5+l+E7wKs4K/Eb/DUuUKcsd7gO5O8ldhlWGf5mpF3Br8Rd+m86bwDu+G3J/lYfVvSjwn6eSD9KFBXIfvJRl/wnNHeqq8a/qi3Quyb83xR12inhp6ZroXoWQ2dK5mj5JwTa07Z3Kpz7tV578iL9561z199Bz79fsgds6IFhA== +1438614003.950,1.000,0.000,HISTggAAAL142u2WSwoCQQxEk86080HQA3gXzyZ4Ay/qEVzYbgoeQXDARWpTdCafmk4mzOX+uJmdzvZGDPbB7fq0QqFQKBQKhX+El46vdDicPfEP8J8+/4vw/CDcIU7tkdSdB68Q55J3Fl5E1yKsceo3QZ4V4jaIDzl3sa9wP3Oig1j9Q/J30LOJ/Qj6G/RB+9mg/2q3xC+SejqHNKct0UdzT3lach80rwbvb1DHk++Y9GX7gfxjpz201z799V72F3sxBOc= +1438614004.950,1.000,0.001,HISTggAAANR42u1VyQ0CMQy0ncRZVjwogALogtqQ6IBGKYEHyWekkRdxSEiej5XNjMfrXMfr7SJyOMkTZUQd0c53SSQSiUQikUhw6If0+qaPAa8EPjNWMi6QV4Ix+qJ+GbERHosKeaZ+BZ4T3vyfDnUUyIPfF6Kf/B3w9sBfQdcgol+HvJ34NJivwKsQndThpM+4bgZ6A70H62BQj5CxBfupBvU1sr+U8Nk+Y+fOgnMi0CcN/C3oF/o7qUM31s/6roGvbVyn6J6yF+uM5vXL9/Kv34G/ff8e+T8FIw== +1438614005.950,1.000,0.001,HISTggAAANV42u1WyQ0CMQz0kWRZDmkLoBdqQ6IDGqMUSuBB8hlptOHQahGej2XH9ji25OR4uZ5Fpps84VVqlXa6SyAQCAQCgcCS0JXWowvfSz/kMaIL/PswzklcIf2wGb4MPInENd4B4lq+DcQnsI8Qn6DuAjwFeAbCk8GO9SHvFvK3PFOVuyr34DeSeg/Al0ldmdyL9SVD/xP4Cfg50XEeTubP5m7E38jctDOfE3si+Zz4GfGTmXMl0l+sW9/cS9qps/0gnfbe/fitPfrr78za3re/+Vc8ALYEBc8= +1438614006.950,1.000,0.001,HISTggAAANB42u1WyQ0CMRCLJwd7PKAAetm2+CLRAY1SAg+SjyUrKy1oAY0/o0Qez5FRkvPtfg3hdAkvxGpRrS2P4HA4HA6HY19gIw/ep5/KHxvPGSt5Vm2if2Ds6BnZxi9CL3f8D+TP+4PQm8TaSK/ZmXRHyrdQvEHoj6TD9U8d3Uz+zJ9Jt+0fSaeIOFH0J4t8iuCZOBdQHKP5Yl4SfIi5S2Jtgm9inoPoS+jkk4mXRByuD528VN1KR/UTnfvAVvLw5fcr3nxvfurdxE79+dd3FE9M4wV/ +1438614007.950,1.000,0.001,HISTggAAANF42u1WSQ7CMAz0kpCAQOIBPKQ33obED/rRPoED4TLSyL20AuS5jBrHE8dxndye80PkOskbPlgH232RRCKRSCQSiX+A7qyHdtt4/SgeJ+OG7z/4VvAvwEbsTvbthJnOYXCD8Qrcwb/B+GfeafAR9FHvDDoX0O/AOB/jMNAv4N8CZvnoJC+d5LMGcTis4yR+dk7svA3ms3pzYi9BHbE6ruBXgjpX4sf2hXpG/ksl9SBk/x7osvhlpb8GcVrQd9b2U9uo/+7VN3/lPvqae/YFL+4FJA== +1438614008.950,1.000,0.001,HISTggAAAMZ42u2WTQrCQAyFJxOd/ijoAVx4E88meAMv6hFc2G4+eIQBBaF5m5Bk8pImbaaXx/NeyvlaPvBF2iLr7VUSiUQikUgktgz783qUTnsN/IZze9h3gb/if7LBr/hU3opztDfoI8457ORf5QC+A/TVfxJ5BpGvCZ4j9AlxlDN4JvSliX6Pol7GcS6zmHcL5uewM4+DrwbvpYs4lc+DeAvyqO8l4nXRrxLUX4Ln9KDOXtm7R6L90xtvnXzf3pf2o/272XvxDTOqBQ4= +1438614009.950,1.000,0.001,HISTggAAAM542u1XQQoCMQxMmrRdEcEH+BffJvgDP+oTvHQvA0O6uqBg5lKSTpLJtg3s5f64iZxlwMaqYy3XpyQSiUQikfgPaPb7lXiWpxAeiyskLqrLeEbyNrCNrOu+g98hj4OOCn4P6hwgzsCuwOvAW+0F7ErqHEEX8hv4kXcCu5N+O9HXyD7qXYLzwHOwoC+fPO9G7k0l+Qr7DwHbg7hK9GhwHzXwM12z70MJf7bf2XmAOiT4DhK8e9s493TjfNQ356zuNH/3muf6Ib/Ib0JfvSYE/w== +1438614010.950,1.000,0.001,HISTggAAANN42u2XzQ0CQQiFgZmd1ejBAizCDqzNxA5s1BI8uHv5DIHowWzCuxAG5vEThuye74+byOkib7RF6iLt+pRCoVAoFAqFLUMDfWv5W+Bv+K7z7BrI5viTh/GGE99g7w7/5NinJM+M8x30jvvkn3Fvgj6gr/IIf8oG/rXePXgH/JjXjLwPqIv5N/AMx591kcfre3fmwWCXZH86+sM4H/8rSXs0N7w3nHchzry24H1Z8H40uQc0ud9akIcF+8Sza5BPdv9mz+XLOP/az1uN/yuPvgBHRQUa +1438614011.950,1.000,0.001,HISTggAAAM542u2USwoCMRBE0/mqoIjruYtX8SqCN/CiHsGFmc3DIhFBELo2TfpbU9PJcrtfQzhewgupW+s2nh/B4XA4HA6H4xcwl+ArfaLIN8RtUKf6rP4i+iSRX7vNwjbks0+Bv6E+IU/5t4jvB7wyeO3Ql3wq7Alzmziv/DbwH+BXOhbEm+ifBc8qdOb/po4Fc1jXhF7UVeltIs79UnuTxJwi9p37NroHaXBW35HQl3yyuGcm6tX7YMLmyfcmDubYh3WzfP/l/Z3lH4PjrZ5PscUFXg== +1438614012.950,1.000,0.001,HISTggAAAMp42u1W2w3CQAzLo1zbLxZgF2ZDYoMuxEiMwAe9H0uWQbpKCMU/Ue6c1LXSu17u283s/LA3co++x7g+rVAoFAqFwn/AB/PKz2N99A/XWa7qe5wgT1gP/D8kPIM8Rd1M+mPfE4kBfXr9AryJ8Pt+g3yBugnWkT/Dfgrd+FysX8U++od6Vnhf5lcjPPQvBR/7B9GVhI/zEiTHuW3kuUbmzgW/kblVutT8K30m/EjxXQfRx/gpfBl1nvmX59hR5/Kv3Wc+SJe8f17LogWe +1438614013.950,1.000,0.000,HISTggAAAMx42u1UQQ7CMAyrk6xsCIkP8BfehsQPuPBMnsCB7WJkZXAAgeJL1CqO3SjN4Xw5tba/tgd8jpijHW+tUCgUCoVC4R+BH9Mzce8v1gfx+B6k5+Iewt8SB+J3igs/Ev6GYoi8EJF5W+KNdJ6Ef1XHqY5T3ih8gvQm0umksxN9NNFn7lMnH128kyP3hXmDqBvJPIXQCTFnPB8h5s9FniV5SqclfYJ4F0QdJP/QxP+25J9m/rFyLyHxu3afrd1H/uV9izf3Mz7k74l3B4AmBXc= +1438614014.950,1.000,0.001,HISTggAAANV42u1WSwpCMQzMp78nLjyAd/FsDzyA4EU9ggu7GhjyFH0gZDZDknaSlib0fL2vIqebvOCTdbJdHpJIJBKJRCKR2A/24T4lOroxjxLb4J/oZD/mK5Pb5Ap+A2b+hcQrOVeBuJE6GtHvsG6AXWHdEexG1jXCmAf1DhBHvQH3PuA8Dvsc8i7gb6DnRKeTe+7k3eB7wTqU6JXgPVawC6lbiB/1W9A/SurRoE4N+kQJs76woI9ZPSyfB3PCg/uQYN7oxrn07pyTnXR0Y/zX9X1L/9+gT2NiBX8= +1438614015.950,1.000,0.001,HISTggAAANB42u1Xyw3CMAx1bMctCMQC7NLZkNiA5RiDETiQ05OeHPWAevC7PMXusx0rv96fr4fI7S0/2OA2WLePFAqFQqFQKBQ42p/iKfFromv4voN3X/a9gU5JfqzDB3fgIPYOuoD8J8jTybw6jBeIu5L6Avwd9Auxow7jnwdfQX8heux3JHkd+rOCP0BnpH9B+hmkr0b6Z4SVjHHdOvkvyey4Xn1yPxlZ10bmrZP7ycHvJI4DS5KH7eesLp08Zxph2RnnKOcqqz+b/1Hvk731tS8P9AXD +1438614016.950,1.000,0.001,HISTggAAANF42u1WQQ4CMQgstFA1HvYB/sW3mfgDP+oTPLi9TDKhNTF7YS6EdoCh6dK9PV+PUratfFF3K7vV+7skEolEIpFIzECyv5/yyWI9mdSDPA3iKvAayVeJVeI7+Ebq4H+oge/AH/sd4jvsnyAPxg//TPJewHeif8RfgeegowNfSR8G8Z3ow77wPBvUV6K/ET1G9i2o78ArpD5bZ/fXgvtXQIcEfCX94XeBvpN7W4l+I+uymKcGc4Kdm07OC12cL9Fcqn+en0fNeTlI31HvrXwAOcsE3Q== +1438614017.950,1.000,0.000,HISTggAAAMh42u2W0Q3CMAxEYydtWhBiAYboBsyGxAYsygh80P6c9GSBivjx/Zzq+M5N4kS53B+3Us5LeaOubCv79VkSiUQikUjsA8slyP3+IM/g277UFXjvkd4D/TbeAt3GI+hn8anCqmvC08qD5HXw2/KPUn+GOl38B9GfwMfFb5b6XfggeUMwvxHGKU7zmOB/O+SZxLUPHPwc9q8GfaR1RujLFvSlgb9Dn1VYd4e4gb4F57YF60X50XkjUJ0Cfr7zvWd/vm9/5WMvl9UFNg== +1438614018.950,1.000,0.000,HISTggAAAMR42u1UQQ4CIQxsCwusJvoB/+LbTDx496M+wYNwmWRS1qyeOpcJ7dAOhXC5P28i54d8kDprZ7u+JBAIBAKBQCCwH3Qy7/HWuga65Ogz6Nj+oavEz8gvkMe6C6wxX8kcsP+oUyDfOp+gfiPrFepU4k9gH/Y1iON5D6A/Qr+VzCMDF4crcCbzKeAvk3tvZO5C5qRO3NMlolPnXRuZG7sXJXqMM8z6ZO/ciB9x6gg5v+dbv+S9/73Zf+tf//Gv+m329wbJ2gVJ +1438614019.950,1.000,0.000,HISTggAAAMp42u2WTQ4CIQyFKYXxL8YLeJe5glcy8QZe1CO4cNh8yQsjMbOxb/MCtK8FWsL18byndLmlD3xhWzjPrxQIBAKBQCDwT7Af+amxDfrljl7u2I3u0/BPbPoT7AqY/8oK+0noUn8n5hVX6BYxPsK++R+w7lgvyItMHeZP+xbnhPkzmOtV5M881Tka5g1+Wdx/i7cX4yJY3a+qkyzurYi6cVH/WeTvon5p7+J82Hd1ZX+quNbpS1/Zz0pn1N82fke3jvPtezwc5w0oDwVL +1438614020.950,1.000,0.001,HISTggAAAM542u1W2w3CMAyMnWcLiAW6C7MhsQHLMBYj8EHzc9LJQQUkhO/HsmP7XMdNslyu5xCOt/BEXKWsUk/34HA4HA6H47sQb8Ff7K+8eU5Q10F+If7dnomfJTsKvDNRJojLYI+QpxjrDfKgvZB3b7fvIf8EegMd66nAfwC9gR/WO8H6DOsZ+lWBfwf2Svoxk7oE4uLg/kTY90rmJhnzIoQ3kO/PxnyxeY+Eh/Grkdeq04pPpA9q/O86WM+r94xsPM9kMJ9s5P/U+fqz98sDRusFlw== +1438614021.950,1.000,0.001,HISTggAAAM142u1WQQ4CMQgESm1dD37Av/g2E3/g53yGT/BgvUwyoRt7MWEuk7YwsITSvdwfN5HzUz4og3WwXV+SSCQSiUQikVgPBf7VXwNdhf89pmNgN8sGOo3Eddh3sHdg1LfAXoPzA3AB+23wCfw68Eb0Ouh2ks/Xvw4+ku9ucN5I/SrEU9hnfg51ddIPlfSHEbsKdfGgzzTIx0i/VpJHCeIY0WVxjeSJ98xI3zMdIXk60WH3VSbXEswd3bmWybmjO/OQRfZRHVbN8b95b96lRwWl +1438614022.950,1.000,0.001,HISTggAAANh42u1W2w3CMAy08yqUgjoAuzAbEhuwHGMwAh/EPyedXIqQKuH7OSVx7HPiujnf7leR+SFv5M7aOV2eEggEAoFAILAFaOhapYuxwPvPkIidwnqC/TYuxK52buAP358NuDqcSRzjPVnfAU+dB5g3+xPRdYC80X8j8U332PlI9k8wPxJ9lZxfg3PIoHcAuwLjTO67Oveu4F+JHyF1UEh8ceqX1Tery+TUdyF6ipOXOv2B1YMSO/YdqrN/aX8Q53485C/jykr9svDcftXHt9r//+U/93EeL+5SBcU= +1438614023.950,1.000,0.000,HISTggAAAMx42u1WSQ7CMAz0kqSlQuID/IVvcUXiB3y0T+BAymGkkVNRbp6LFXc8E7tRlOvz9RC53OUD71F7tNsqiUQikUgkEonjoX/W+b7nSNxQiU4BvpP3ogQ+BXy2dQO9Svioh/kJ6lCvAs/AfwLe3OOJ8DDfgvwC+mfQW0gd7qOSfnGeyGN9GpmXET0lvg51GvwvI3rs3Cj4MH4j9UbOqRN9C9aFnH8n+8A5MJRgTkr6EcLfe8848ZNB/1G/0e8a9Bnlf71f7SCdvfX2BhW4BWc= +1438614024.950,1.000,0.001,HISTggAAAM942u1WSw4CIQyl0M4gcTEH8C6ezcQbeFGP4ELYvOSlfjBxkr5N09LPKwNlTtfbJaVtS0+ULqXLfL6nQCAQCAQC+4QEv13sw7e8hchX1wvoGfysSwW7gn+FfJn8X2LcQuJW4qfAy6A+8qwOX4N6Qz9A3QX8hmyw3iD/Cv1hvur0M+KPkKeRvpTYjXwne5PXSvIL4WFgz4QXO4fmnCdWD8+3knugDj9x7Ap1cF/FyVece4pxZdKcYMjOHPlUn8Vv1lz/1bsgk/z/7d2SB+a+BQA= +1438614025.950,1.000,0.000,HISTggAAAM542u1XOQ7CQAz0kXgXUvABHkKXtyHxAz7KEyhImpFGzko0CE9jrZOxR86spVwfz7vI5SYf+BZ1i7a+pFAoFAqFQuGXoH+mB+sbRIUz40+Ez/o48CbSb48nOAfwOjyfIe7vNTgH9HXgLZBfQM8MdYzU7URXh7qN6DxD3QAdWQwyhyDfg80pSB7n7FDHiF8a4QfxVeYPTfzkxCf4H4N+EKKH5dFHSvqzvnhfIrmHktw7O9hHEr4N7pFsv2T6R/eYHsxnur+1p0f3t74BJEAFMg== +1438614026.950,1.000,0.001,HISTggAAANV42u1WOw7CMAy1E6dpWLgAd+FsSNyAizIyMpAyPOnJAVq6+C2WW3/jT3K63i4ix4e8kDvVTtP5LoFAIBAIBAJbQjeW39vuqL9P+W/zeb/z4P23wBy5Qr5n4hf1MtE3oJXoofzU6QH4DDzaW+gMfIM8KrE/A9+IXfxfgW+QD8pVkofBORbHjkFcRs4V6yukPhORT0Qe+wrrI6S+megriU8gDnX6Vp3+nEjcicyJR43MozpzWxz/Mmg/De4Fb6/oj/tNyP5Zey8nWQf6Z7297id9AgRsBe4= +1438614027.950,1.000,0.001,HISTggAAANh42u2XTQ7CIBCFBwZKdWE8gAdx17OZeAMv6hFcWDdf8gKLRqPO27ww0Dc/DJCerreL2fFsT/jKaeW83C0QCAQCgcBvI71ZJ/1ZXbb2T3sW82Suqx0dh51cVp5gd+gXxPGa32E+i/Hc0W0YV7Ajzr3girgmcBN6Dd838X1BPrPQaSKeivUH4a+Kuql6MS/uP/uA+bjw42If+L8xiX4tYh37MHX6bPScmDgvLvzlQd0CVn5SJy4T9TOR7+h9oZDFWNnTYB65U/et35Gt7/Fveb8+9m49AGL7BSk= +1438614028.950,1.000,0.001,HISTggAAAMt42u1X2w3CMAy0nUcT+GABdmG2SmzAIozGCHzQ/Jx0ckAIAfL9nJy6PsetnfZ4vqwih6s8kDbWje10k0AgEAgEAoFfhn5pXjaZ51jPZJ2xOLa9WC+D70aD/BLwAvbwKyReBc4QRx3/4uh14EauG9hDfw/xmxO3Q/xKdBa4D/edgXdgN1JvBf9E6o92heeOdSygV4ku2p39dzi8gK4SHXyv02Sf2JP5sP0Wp+/KpL6SflJnfpijz9gcf3Hmxqfmsb453r+cP3oH16kFnA== +1438614029.950,1.000,0.001,HISTggAAAMt42u2W3Q3CMAyEY+en7QMSA7ALsyF1AxZgREbggTx9kuVWRBQJ38upqe/OTZSkl/V+S+n8SG/kztJZr88UCAQCgUAg8E+Qg3LUqdeNPrJTz/+/gnHqq6EjZ/hVw7eCG+r4PIMz9EvnyfCZUMd8+jfUq5HPnAJe8P7k9NUcno35KUZ/1CvWi/PH/sVYR+aqwfRRQ5+d3GLoxcn3+uH3NqM+OX7q7Iut/SRDX5x9Lc64fHjOjdLJzjoZ1Oe3zvdR83d0vz97T78AFF4FjQ== +1438614030.950,1.000,0.001,HISTggAAANV42u1WSw4CMQiFlnY6RpM5gHfxbCYu3HtRj+DCzuYlL2iNRiNvQ/iWAYZ2f7ocRZaz3JE71U7T4SqBQCAQCPwqNEoQ+MI50ME8mT4/aJeACuGz469gVxx9JudX8Dd8h4KdAV+J3dTpFuQbiIN8Af8ZqJG8J5IX5rPm2cAf7XdgVx1+zWsBeYNzjfQJ9TPpSyN9xb4VMk8GcnPmUsl8JTJX6swf9jmTOogTl1FzvkOc/yI78ZPTv/RknUb3EauXt7f0xX05uh8/fX/om+L+7f19AxF0BXk= +1438614031.950,1.000,0.001,HISTggAAAMp42u2Wyw3CMBBEvf5hwgEKoBfaoB0kOqBRSuCAc3nSyAEpCCk7l5Hj9c56so5zvj9uIZyu4Y3U2TrHyzM4HA6Hw+FwbBEGXhr/rc7ouQ04DuKj4Bm5cxH/hUnUmZCPY6VPvR3GWXBFfBX1ts6TiEuIa9CfhB7r3Xc+IL5invka6pj3dxS+J+GH8qFAh/ugvvInYR19LsIX1RfsI65X+2a/xkHf2CDvp+eF83nheVR1mHivSjeIutf+Dv1rHlvZj1/dL5u7V18KHAVF +1438614032.950,1.000,0.001,HISTggAAAM142u1Xyw3DIAz1Bwrk1AG6S2er1A26VMfpCD2UXp70ZBTlEsnvYuVhPwx2ILk9Xw+R61t+8Gl1Wrt/JJFIJBKJxDmguQWnhJE6alBfhe+3VX0Dq8RiXAXe4bkFfg34v98geh34ArYR3kG/wngHfpv2AvlsJK6Q9Q4yfwM7QA/jGI/zdTJ/IXGYtwJfSf1w3En/sTqgn5M+FJKXkXwQTF+Cfregb4z9Jy2uz4Pzmumtvsca5KGL54vs9JOdOkfprsIP1st7+xz7pF+ULAWu +1438614033.950,1.000,0.001,HISTggAAAMx42u2WTQ7CQAiFGYbWVhdewLt4NhNv4EVdunRh3XzJC1008Se8DQFm4AEtmdP1djE7PuyFvsi2SD/frVAoFAqFQuGf0b4krjrvia7uu/A73n30B/wh4u4WOcLuiPeWA+JS570R+gF5yWMG3wn+gD4hP+uYIXl+gn8PGaJPg7DHSjv9o+h3iLmEmLOLuQ+oc0jmZyKf+g7JuwvdoPeEryfxQvBQfWpJHdl/5Uk+E3zVPmhJ/yzhvda+1d7rK/fYp/f2r/Lwreb3BKGTBcw= +1438614034.950,1.000,0.000,HISTggAAAMZ42u2WQQ4CMQhFC7QdRhdewEN4A89m4g28qEdwYd385IemzpK3IWn5hTKUzPX5epRyuZUvNqwMq/d3SZIkSZIkSY5HFv10Ul/BL/JXsKg38r/4ow3rRNfBr8F6Jec7rDvoN9B3sEbi7UR/Jv4nonfY3+AcvLdBvH3yPk6+A6urBXVppO6N9I1BfxjpGyf1N+gXjBf1XSX7LfBXkreRehnJh72/HrwjnXxfmC/Dgjmhk3NEFueSHDzv5OA4/87X1fjyAXbVBR4= +1438614035.950,1.000,0.001,HISTggAAANB42u2WzQ3CMAyFnZ8mLT2wALswGxIbsBxjMAIH2ssnPRlEevO7PNmJHdt5qXq5P25m56d9UDZOG+frywKBQCAQCAQC3yOBj85Lu+7/cWJfdmxyEf6MdeaZ4K/Yv4j13W7IU2EXcEXcCft3njfuwr8iX8c662roZ0VdHfV05F9EvU2cx3kvoh/OuyFuEnNr8K+I6+KeknOf1Ocs9GWO7qrQZ0F8EzpU+jehpyy4Ou/P68Oc95edd/6r/e93Z1R8+vO8o/ocPZ/RfY+KS28gswW6 +1438614036.950,1.000,0.000,HISTggAAAMF42u1UyQ0CMQz0kWOBBw3QC7SGRAc0uiXwIHxGGgXzi/B8Rusdj3ed2JfH8y5yvskbPlgH23WXRCKRSCQS/wVd3H+1PmuwXzqJf+tvRO+EG/Gt4NchjnkF4k58nDwXiOuk7jb4AFzBr4BPB30nfvg/nXzPR3cafCT5qCugr6Q+q9egXwV0Fe4B5ju5H21yTkbyFd6jTsh9EHL+NvHTYJ6SOTHSBzZvWHc2XxqcbzbPGtw/v+4lCe6h1RHu7wtlIQUL +1438614037.950,1.000,0.001,HISTggAAAM942u1WOw7CMAxNnp22DIgLcBfOhsQNuCgjIwPN8qSnmBapi99i+dPn2ondXh/PeymXd/nCVllXidurJBKJRCKRSCT2o26Mr/Sf1gGh93gP5gfxg2QjvxPfLPxdn4S/yxPFeZAHlH8i+0L8s5BGdTbBu5CfdRd5Oe5M/E3U2UR/jHhnilPnxv3ic2R+E/epij67yFvJDmFX/C7mAYN7z/NiQX4M5tHE3HB+BOvC4L0x2Av4cZ8guB9G+woH7cOoH3/Ke9RzW+ve/Z36ADHcBe4= +1438614038.950,1.000,0.000,HISTggAAANB42u2XzQ3CMAyFE6dJU37UBToAWzAbEhuwKCNwoLl80lOQSsUBv8uTE8fPTW2rXe6PWwjzJbyRVo4r2/UZHA6Hw+FwOP4BcWf/b+uSDd9zAbZ17EHkQ7v5jYiTwaljk1vcAjtjfUK8UZw/4HyzK+I0Pop8Z+jQf4JuWz9jPyNO8z+J+6jwK8iriOdR/qyLCj3DPQ7i/STBGfVCPepE8f+h6renH0V9D6KPUqefVP0nUfep05eqP9U92If9bhvni9pX+e41D381f7fqxBe7FwVA +1438614039.950,1.000,0.001,HISTggAAAOF42u2W3Q3CMAyE7TikLZQBkNiF2ZDYgKUYhxF4ILycdEp/EKrQfS8np7Vjt4mT8+1+NTs97E1U9arp8jQhhBBCCNHGV/r5zPeYBrEZidgJ74UQL4N6w96RvErVoeoI4wHaNewM/jvQPdgFxvuqB1JnAcXnI+TzmecI8Tuoe4Dv3IN/wHgieQWpG+MkEgfXE/MLsj5yI34m7wWZJzfmt4a/k/lKY90H2ZdB4vnMPNPEfTx1/1sjrk30X9v/0sJ+6Sv747f7ri+sw2b+j1+dM1uJ/y/nthBCCCHEZu83L3miBbc= +1438614040.950,1.000,0.001,HISTggAAANV42u1WSQ6DMAx04mwtqtTe+QtvQ+oP+tE+oZfkMtLIVBWUg+cS2UzGdnAM8/O1ijykQ/sa+hqXtzgcDofD4XA4/odAbP2Sz/al8d9HVqZTgYc6Cn60MZ8M/gzPo2EPfgG/FbfB/jvUV0H3Cn4FOwF/6F+Ah3ErqWPgBvoT8BuJh3rsXAt5j43EqYY+9ksh/aJGnzJdJf2uhId9HUifK+FhnGj0mRBdNXSTcV+zcf9l47xg9Vnny3SVnOev847le9TcDRvzk53q3mvfWb5np83zA3wDBPg= +1438614041.950,1.000,0.001,HISTggAAAMt42u2USw7CMAxEY6cp/Ww4AHfhGlwHiRtwUY7AgnQz0lOCukBUns2oTmYcu4kvj+c9pfMtfZArW2W/vlIgEAgEAoFAD+xgeX4Nb9RtnX0iH2LSD7I+wL4M3xl8dH2qXCQ+wv4t/1p5ljyat4jfBDyKn+o2XiCewbfAeZeGfpS+ruKnPItuq/8k7LLPoG8D3JMC/Tb4Xw73SH28kc8gn+qJM8QT1OOQtzdO7yzDu/eGjvQ0R6xzvhj42s559K3uqHPedq7/ff1vgacFLw== +1438614042.950,1.000,0.001,HISTggAAAM542u1UQQ4CMQgEym5dXbMf2L/4NhN/4Cc9+gQP1sskkxKz6kHmMimlDBDoermeRZabPFEaa2M73SWRSCQSiUTiG9A/rduCfUE2Yi9BHe/4aYcdGPWRHeIPpE6Hd+hvcD+SfFB3Bn/kqXEFvR3p+xH8Mc7rvJC4Ffz3oFdJ/ROJM8N7gzwG4APwCPEczoXYnfQH83Yyh+jvZA7ZXNbgvLD8enYP7oME65NgHCH73NtHDepKcP/Zf2SdenWj/11//P9+Oj/bSOft/j4ArTgF4A== +1438614043.950,1.000,0.000,HISTggAAAM142u1W2w3DIAw0JjyaVuoC3SWzVeoAlbJoRuhHw89JJxORj6ry/VjmzsaAA3m81qfI/S1fxN2G3eqyicPhcDgcDsc/IwzyZ8+HOtTrYH2RxGunP4HfbAY+EV3jC/j4HzqR+SqMZ2Meq75K+BnqnEFXoe4E+tKpx7gbqa/xV1JPsxfwk3EuBeJwX/FcsG7sj0T6DPOwfVPgMxnXzvVZ/c6+q0z6UEj+QNYnpD+VxKnxnUcjH8t/9J7pvbdG8/fy+iPvQzioO+v9CB8w/gWH +1438614044.950,1.000,0.001,HISTggAAANB42u1X2wkCQQxMNvvwQMEC7MXaBDuwOcuwBD9cfwaG8UBFMPMzJOTJZnN7h/PlZLa/2gMx2SeX480SiUQikUgkEu+DAxuRlb4AG7zn0M4FY7xK/Fn+IP4h7BvhAXGevEzukzcgd7BvoN9BfGaPdbB6hvDD/FvQB/SxgFxI/0PU0cG/gdwhfyXn1sR5BsRzoq+kLsZO6mPzyeYM58RJf2x+8P+orswf5J6x/jCPvXh/2f20lXGM1Kf2l9p3ReRRe+5X9/in7L8d72++v3dw3gXi +1438614045.950,1.000,0.000,HISTggAAAMp42u1WWwoCMQzMxHbdquAFvMtewSsJ3sCLegQ/3PoxMKQLCn5kfkKneTRpGnq5P25m56u9sVslVunL0xKJRCKRSCQS3wd+5O/zj6P/nYk1iC/Cfia+iXgu7Pe0VvGc+K5XSbLfImTXO4j4Pa8p4FXczh+Jb3SOE52jkf+Z7CM5kR+uQwv0OT8X91IFz/fppF/IP4Q+SB+iT2vQZ1Hfu3g3UR960LcQ+wjeuW/MB4PzA4E/DNYlilM22kV12DrfbLDO/zKn8QIyAQVN +1438614046.950,1.000,0.001,HISTggAAAN542u2UTQ7CQAiFh/m16hFceZFeTRNv4EU9ggtx85IXGjdN7fs2hIFhoFAuj+c9pestfSguzWWeX0kIIYQQ+8BWfs82Wsda36sQe4ZzptvCe8wP9UzyKoFfIe8MYq8um8sOfl/7RN6fSJwC9rPLg8uTyyPYO+gYt0Ec9B8Qt5O6OsQZkG+H8wF5V3K/kr530GswF6yfJZgDjI/zU4M8MO9GZA72g5E5smCekUbqif6zHNRtpF+snhzUmX/cP7ZwP6WFe2Zv+902Wp/9WR+EEEIIIYQQQgghxLrYG51yBXc= +1438614047.950,1.000,0.001,HISTggAAANZ42u2WwQnDMAxFZdmJnZ46QHdpVyt0gy7aEXpocvnwcAKmFKp/EZKlny8si1wez7vZ+WYf5NWm1fr1ZYFAIBAIBAL/jDQovvdc4Tt5vHOewBapT+IXqMvw/5iBf/MnsS51s/hbXoN4kTj5VeoW0LPlnURPg3gVmyVf+9T+quhR3iJ8s9hF+CfRWYFP770Bj8M8zHA+de65yFzSfdI8muRneAc0jw7foTknHd6ZX9JjoI909PYGvb+jddSfAS/tlaP7cfS+tC/t5dF6en7+Mf3d/t6w/gUs +1438614048.950,1.000,0.001,HISTggAAANN42u2XzQ3CMAyFEydpS+HAAAzQLZgNiQ1YlBE40Erok56cVuLmd3mq459nN43S2/P1SOm6pC/Kynllu79TIBAIBAKBwO/9IPr8T52jz4rpZ7CbE18d3Rn3x4Y4rlfULeBh5RGs8lasD7BPyMP1GVzht8Wf8FyF3gms+rkIPVudM+YyI55zbKjL99FgH4V/EXM3Me8m/h+SsFO37dy3an9mUU/5s98k+lP7l3msU4d1ngfZya/ml8T37aF0+pmT1ztfjp6DvXHWqX+vnoAzrw/QDQUR +1438614049.950,1.000,0.000,HISTggAAAMh42u1WwQ3CMAx0HKdpQIIF2IXZkBgAiUUZgQfJ56STG0R5+T6n2pez29hSL/fnTeT8kA9y59RZry8JBAKBQCAQCMxDJ/XJiRv4JsJCdEry2fFV9p8IPPpbQF8gb+S9hn4ldQc3qFPArwBXoi8Qb1B/nDtAvJBnPL8Ar6BrpE/0OXU+gs5Iner0qaCr5D6VfF915iMTHyH3ZNAnm2ucE3PmXjfujzl1t+4JMvbJ+svO3jNfb+/ly/ysXn6c30u3F/5VP70BgqwFhQ== +1438614050.950,1.000,0.001,HISTggAAAM942u1WSQ7CMAx07CZpBRIf4C+8rRI/4JM98gQOJJeRRg4QcfJcRna9TOvG7fX+2EUuh7xhjVNjvT0lEAgEAoFAIPA/JGKjX4n/07oL+Q8U8GNfA0Y9GJeJrUQP9lsbV1KnQFwB7nU3YIU6nc+Qn6H/RvwL0dHjTyQvE70V9FSon8kcK3kuOC82/+LMr0AdcfSoY+N7buS6OedBQV9y7lsn6U2D54TlMV0sDuNH94ORPWCD+0LI3NOkPcegX/YZ7S8/9p+la5b+4e/CC1BxBdE= +1438614051.950,1.000,0.001,HISTggAAAN942u1XOw7CMAyN7SQt5aMegLtwtkrcgIsyMjKQLE96SlNADPgtlu34E9d22/P1toQwP8ILVqgUqpd7cDgcDofD4XD0QwgvH/bP/CqxM3KuyiPIlcRV4DPYV/kA9hEofn9G8KdEPxaaQD4VeoT4mfifIE4GvwniZUKr/rDSrupPkBfep+a/A397Uge870DqnqCuyBvQROyN/UeQfkuNc0r6EO1ZXyjJS0kdpCFXMmdG+ED6m82TNuZVGnNoJE8272v3Se9+69Vv3Z+90DfvIxvr8626/Op5/M17+wnxswXp +1438614052.950,1.000,0.000,HISTggAAANF42u1WSw4CIQylfMqAGi/gXTybiQcw8aIewYW4eclLh8zMrm/z0h+0FBpuz/cjhOsr/JAGy+B4/wSHw+FwOBwOx3rIpH5vO+Ow0v7/D0biV4AV5Ax6hfUj6CvsW2GdSPIpIHeIV5LvMvgM+kbqugw+QbyQ82rgVyCvhdiV5NNgH4Xz6Ub+CvEd4vEcM+lHNe4N9quCnIx+ZiJHwon0Ae8TrhOg7kTqisY7ULBno95E3kEmdQjx3+vdW/NFNs6fWT/ZOD9n/Y6a+3JQfvIFetYFjw== +1438614053.950,1.000,0.000,HISTggAAAMZ42u2WTQ4CIQyFKcOPrLyAd/Fabk28gRf1CC6EzUu+gBPHVd+moeW1bwo0c3k87yGcb+GDrVvrNl5fweFwOBwOh8PBsIPzxMnaxK9r/b8z2Ec6DPyDl6WO1kvdFtivfvquk+wbtkq8SN0qeSroJX1aV/MOXhNeBX8D3Rn6Rfma8G3Ci5Jf65bJ/UnQtyrxBHy6h3QOEc4zQnyb3M+0qId06/vKwIsQX+1DgrxpcX7Qu947v2znnPuWZ+E/sB/5D9P3Buy5BVc= +1438614054.950,1.000,0.001,HISTggAAAM542u2Wyw0CMQxEnS+7ERIN0Au1IdEBDVICJXAge3nSKLDa3DyXkT+xJ1/l+njezS4v+yJ1Dp3j7W0Oh8PhcDgcjvkIwqY/Cr+KB/HPY5+4M492Ef23vAqOGFfVv1Tkb/EM/6nzKvzUcUbeirqsz3rU32CzThXcUHfjBdyEDupk3wRm3QxbrX8U+8hzsYi+WexnEuuUxbngPE2ci9H9KGI8dXO+JvJUXPUPg3ttg32wwb2Lf75D1FF+1HXU+7c3P9hchIP6zNY57P8BtpEFyA== +1438614055.950,1.000,0.000,HISTggAAAMV42u1UOQ7CQAy0vSaES3yAv/A2pBTp+ShPoCCmGGnkDVJE42ksH+PYk929Tc+HyHWWD9pidbF2f0mhUCgUCoXfoCVBYYPzo4nN6i2xjfjIV8g3+C7jNZIP36F+BzbqjhDfEx/jwTuA74Q3AD/8EeYP/0TmOEMd63+B+Qx448p9neiY6cv+t5O+RvoOEHfSd+25xHnYOWR7CIlrwjOyd+89wbk1iUtyz7P9sneB6c3yQvSTTn17+brR+9mry7/x1e0NcrUFdQ== +1438614056.950,1.000,0.000,HISTggAAANJ42u2WQQoCMQxF06ZjZxT0At7Fa3gdwRt4UY/gwtbFh0dBBxHM34Sk6U8ySds5Xm8Xs8PZnvAmU5P5dLdAIBAIBAL/jRR1fDWfBPrr/+zNfQ525XXQC9j7/s3Ar4j/JOsO9iL8GmduchGp/rPwd3uV9S53kI/GrxK3Qj2ddwt5qJ6Fbw9xquSjPIusq1+Bvhf4btqvCnXoXCWI52BPME8J8tT5z+BnUG+Gvk0Qn+bVBufOBueR6rVBnXSuDe4N//CeJD9fmW+te/RX3pP0AA9KBV8= +1438614057.950,1.000,0.001,HISTggAAAOB42u3V0W3CMBDG8buzk1DKCEzQJVihK1ViAxbtCDyQvHzSJyNUJIr+v5eTLd+dnQRzPF9+Ir6+46atMddYp98AAAD/S7KflzxPmvy8s08N1jfTpwbry+S5/CZxXuNi1nWpo/OTiSVjl1fSX9dv87s17qXOIrHkXE3q7ky9yfT7kPku9ScZb3mfMj6Y5z3J+5rN/mdzjjDn1ufcpV6Y/mXed5rvx62rQV4Mxs18JzkYa71+5/n6oG4M8ke//zT7iQfvlXzwXvrr+/JZfd7tfwcAAAAAAAAAAAB4JXkFXBIFQg== +1438614058.950,1.000,0.001,HISTggAAANd42u2XPQ7CMAyFYydpmwqJjYm7cDYkbsAlGTkCA5me9JQ0/Aih9y2WGzu2Gztqj5frOYTDLTyJVVqVfroHIYQQQnwHU11/VZ8P2lunH9qzeJGso18k34PM3hr7OMhU5QR2qGfQE7FfQZ9BTpDfUuUO1tG/gL5CfFzPJB/Mv4C+J/EXeF5AsveFcazRN5n4G+gz6InsP5HzT0SyvnPSB7mzb2Ojbid15kZ9Pjg3TvqR2bXmL3TOZ2tuR+85e/Fe3Gpvb9r/U/mFjeckhBBCiB/4f3sATIQF3w== +1438614059.950,1.000,0.001,HISTggAAAMx42u2Xyw3CQAxEvXY+mwOiAXqhNqR0QHOUQQkcSDg8abSIJJw8l5G9k9nR/qRc5vvN7PywN2LhsrBfn5ZIJBKJRCKR0Cjgf/lQ72KcfaXrGr4r96ipC+gG+LPvGB/FvPQfRJ6VK3yZi33WVYxX5DyJ9ZmgG0VuchX1CF/mm8A9vovGOrTYxXdqP9W6ujh3LnJGQ8dzG+KcB+oOuU3ck9b96MV8JnJ16r9LzBsb34etum99fKf3sDTeufJjPjsolx28P7ZT3k//Bck6BbA= +1438614060.950,1.000,0.001,HISTggAAAM542u1WywnDMAy1LFt1nUA7QHbpbIVu0EvH7Ag9VLk8eBhKKIToXR6y9U0k28vjeU/p+kpfqLM459s7BQKBQCAQCGwBOXi+spG+EJnpZ2CBd1+B9QL6CdYV9pX4X+UGfhqJs8pG7E/Ok3N1PhM/BewM5IvzDOsdWCGPGfYb4Q55TGBfIW6GPA30OqkP6yxgZ+Q/V+BMvqeSvhHiV0jf6aAvjfjRQRwdxGN9xuph88LisblkcUbz8us5MDof/nVe5p3dD3u7lw7/jvgA5fMFiQ== +1438614061.950,1.000,0.001,HISTggAAAM942u1WSw5CIQwE2iI8Y+IBvItnM/EGXtQjuCkuJpnwMHHz0tk0Le1Aafncnq9HStfkEJfZZbm/UyAQCAQCgf8iR16R/w983/8a6LpzHSOuujTCZ2S+Av9HBbsRO46P+BPoY10b8VewV5Bjng5xjfAZyA7zdOBvZH0b+F9cngmfQf4N9Ap6J/64b0rqaqR+Nuknm9TPJnUupP+E9G0hfadgF9LveeIvZJz1N+PPJF7IudOd51oIfyL7Obs38uK+lcV76Cjvxmp+h32/P75PBOg= +1438614062.950,1.000,0.001,HISTggAAAMt42u1V2w3CMAy0nQcpfLAAuzAbEhuwTMfqCHzQ/Jw4uRURSMj3c2rt2OfEcS73x03kPMsLaWVd2a6LBAKBQCAQCLyD/pl+HbwPOkifEbs5dbD8icTH70S4+2ViL+CXiB70KxvjFtB5INyAJ1jf/U6OfSLxK8Tveo7ABepoZJ8rqbNCvAzciG5cz84vE7/q6MLzyaT/jOTVjf3E7NgHRv6r05eF3J/s3EtWx1727rM5c4Dtt304v3XwPJOdc0oH5/n1O/G1d+0JZMUFkQ== +1438614063.950,1.000,0.000,HISTggAAANJ42u1WwQ3CMAy0ndQl9AEDMEC3YDYkNmBRRuBB+jnplIb2U+T7WHFs5+w4Vm7P10PkMssXqUqt0u5vCQQCgUAg8J/Qg/HRg9fZNtbDOuukDT3yGeA/mEBv7L9YZSbnnGA9gj3Gd1gb2CHPJX6B/QLSgccEegd+6Id8R4hzJfGWOGfwz2A3QT4OeRZSpwx8nOwncq43+sPI/Q/k3hPpE1l5z0pkIv2WO/tRSV1kpZ0Rv9zIQxp8tOHXOxeNvHNmn36cw73zSHeap1v1u833D98xBQk= +1438614064.950,1.000,0.000,HISTggAAAMd42u1WOQ7CQAz0kXizEhIf4C+8DYkf0PBMnkBB0ow0crYIFHiakS17bGed1V7uj5vI+Skf+Mq6sl1fUigUCoVCoXAk9M/mQr8R/wR+xkbecU70nOgbiTMSN0M89jGDv5E6jBvYnfBWZyH1HOwOdoC9gC5yJ30xvQDG74J+hz42PpG8gLgg5zAn5xhkP5z4W7KHE9kPS/ZWyP5leZbsvyb9ZDqx879g8yjJl6Tv0XuS6eyd1wbvr6Pvcf2Szuhc9qO+9Q1YcAWS +1438614065.950,1.000,0.001,HISTggAAAM142u1WWw7CMAxrnLIyQOIC3IWzIXEDLsSROAIftD+WrExsiJ/4x0rSuO5j1S73x62U87N84J2tM66vkkgkEolEIpFYDxM8gKBPxQoQugjGgf4LnfJj/ir6OL/vvBPjalBHMN+BmOcbfOzcKD9RvlJ+6M1Ud9Lh9Zyo3kjPKWYfUcz++Vya8D2J862kw/vn4jxmcZ88uIcm9s2DfTbh38Q9dRGDdItYv9KH+J4h+tR3jKBuC9+FsrDfvtTHSh9bv6O/0t/K19/9vAHr5QWR +1438614066.950,1.000,0.000,HISTggAAAM542u1XOQ4CMQz0kWRZQOID/IW3IVHQ81GeQEHSjDRyQKxoPI3lHDPjXSdSzrfHVeR0lze8R+3RLk9JJBKJRCKRSHwPncz1RzoW8KOOkf1OdMb4QnQcYoX5AnmF8ZE3GN8DrxG9AvsPPe4gNtDD/aO+I/GJPI34b7Deib8K+Qp6K3wHrGcBHvRbAp91sh4n/VBJP7GI/xF94rukwXqm54TfgnnsRwn630lfGzmHRuq1SR0j59dJ/bP3jQb8ttF9JQGPyn+gG6//2M8LiNEFeQ== +1438614067.950,1.000,0.000,HISTggAAAMN42u2Wyw3CQAxEHW82ISGiAnpJbUh0QBOURwkcSDg8NPJK5Oi5WP6O1/vRXu+Pm9nlaR+UTXab9PVliUQikUgkEol2dH/6W/NaeTr88yL/9x8oeByyCnsPf4F9hD4gLsrb/WeR5/Cz311O0Ef0Q/8JchT2Hvok5Iz4GfxV9Levd0E+57+gHvdlCPaV81fnkPEOvTTyVHE+XdSt6Id6EesoAY86v1FdE/fFGufoQV8W8Kh77UJX78FR7+DRdX943qOYBZY= +1438614068.950,1.000,0.001,HISTggAAANV42u1USQ7CMAz0kjQsQkLi2r/wDb6DxA/4KE/gQHIZaZS0qjiA5zJq7cnYWTw/nneRy00+8Mpa2a4vCQQCgUAgEAhw6JfzjOTpIFtHZ4Pr5soJdD74jf/behN8Z5JfIG6gd6ivEL8TySvEZwJu8QOss4M41lVIvOmOlc9Evyf9Z6hngrqcnLeDvgyeQyJ6FvdOPQL+CvlKdKwfJe8mEV8jfbB7LB0/I/5G9slIn8x36TvvzRlbqO9xb46NzkHbeM5uPd/X7vevwCTwT+cdWHkv3rssBUk= +1438614069.950,1.000,0.001,HISTggAAAMd42u1WyQ3DMAwz5diJX12gu3S2At0gi3S0jtBHnQ8BQjncn/ghYMmUFDmy76/1mdLtnX7IndHZHp8UCAQCgUAgEBgPHLTDWb+qbzvj8HrpPHVeSK/y+5LenRNxIXsVzH5Kb4s7U36FWMVZxL5MulucJvZXYTdaL6TL+c3k36jeJurn/nA8/k6slynfIs4BhJ+yQ+hzH82x4+A5S8LPHHsW/nDqMyc/iH5B1H/2f4WTr+eHwfMIg+fk1fl6Fvane2C3zhdorAWP +1438614070.950,1.000,0.001,HISTggAAAN542u1X2w3CMAz0Iw0FtRIDsEtXYCUkNmBRRuCD5uekk4NaIYF8P5Zj++I4qZNe7o+byPkqb/gqdZW2PCWRSCQSiUTin6Ab7XvDQArRNdAHYo/iCrwDhbwLHeYpICvYMa6S+APYndSl8Y1BPg68A/BUIkeIa/oM/I3vSPybPhH+CvFzsI4J/Jv9BPNhvbC+StZRwI7jBvnguUB+C/bPyT47ydMgLyfnVUkeSub5NL8SfKdO9g/HheQV9QO2LgnqoOw/rzO+t5/qxv6pQf/bq89rp9+v32Pf9g/5Xo1IBUg= +1438614071.950,1.000,0.001,HISTggAAAMh42u1XSQrDMAy0FtvpcugD+pe8rdAf9DN9Vp/QQ+PLwKDQQghUcxGWNFpi2SbX++NWyuVZPrBFyiJ1fpVEIpFIJBKJPUD+vA/9Mo4QvhJ/I35D76BvkKfDugZxGshKeEMeCa9BXIM4jfCxDgPZSX1TwGvAH7wz2A/g10lfaD8RvUN9GH8i6wJ8X7nfleyrED2bKzbfTuZAyP+LB34snxO7kfNmwXdby4/OpQb3AetXyPkXUo8G+q3uY/kx317fiaxro37eMtQFpQ== +1438614072.950,1.000,0.005,HISTggAAAQR42u3Xy03DQBAA0P34E4iQKAAppdAObSDRAY3SARwSX0YarQkXLL13GXl3POPY2XXy8vH5Xsrbc7nqt1iv4fL9+lUAANivDo7LzvltvN1Zp+6sv7dfnO/heAl58+C8bXxK8k+hbg/x8RbXEOdkPM7HvkuIW/2H0HcN52/5T2H8lPQ5h7ot1I3XEfv1MD8lfeZQP45ndebkucY+LcRz8rzX5Pn35PsyJ3k96R/vc03OW5LrzmJN6tUkvwyO22A9TIN12wbrOLuv2bruv9w32s79YFSn3pnXDrr//zXvaJ/rv9QFwP8xAMD7EQAA/C4HAAAAAAAAAAAAAAAAAADgCOoPJpAGfg== +1438614073.950,1.000,0.001,HISTggAAANd42u1W2w3CMAy0L2nSlB8GYBfWYQ0kNmBRRuCDIKRDp7QfSBXy/ZwUN+dHXCen2/1qdrzYC6mzd8b5YYFAIBAIBAKBz/to73GpOEH2RGziPci6oPVM65PgLPwmsr/9NKGTyM558XeVeO5cSId1K+k14ccp/kI8i/wb2RfiifJbSBe0vw7Ol+uViQ8iXgi9SvFkkSfXz8S5Y9CHLvpwEv0K0R9Z9LXqT6WDQVwm6q10iqg3Vv6fai74xvk1qrcP9mHlnMKP565vrNO/3RO+U/9f98oT2/8FSA== +1438614074.950,1.000,0.000,HISTggAAAM942u1Wyw3CMAyN7TgtBYkF2KUrsQISG7AoI3Ag5fCkJ7dcioTfxXLkX/xLLvfHrZTztbxhnUqnOj9LIpFIJBKJxD9BdrYvgdznnxbYUcLLRj/L/9DBTgVqRE9B38l5DfjW6QD8BPKNyNfAzhH8nTo9gP6iNwKdgEd/DvrMzkDk2X0N4nQS7wB5H6FeDudK6oB2lNQf+6AG/YF96MFcYP8Y5KkE/tbGryQfBn4auYcRXokfI/MrK+c4mm/5cl/Jxv0jwf7Rnfbvz747L9ycBWo= +1438614075.950,1.000,0.001,HISTggAAANl42u1XOQ7CQAxcH5sNBIkP8Je8DYkf8FFKSgo2zUgjk4PO01hWvDN2vLaS2+N5L+X6Ll9Yt9Ktzq+SSCQSiUQikTgOEjxXiJOdfCxe4fsP4UF+RvwGvoNd9E8Qt9gzObfwVtCtwNfAx/MOOmO3E/ANhKeCbZCPgz+AHp530EF9jB+Bv5H6LhA3EV3s00D6q+R/wYO+KOlzJX01ct8KeT9OeI3cewvucWQ9mE8lcybBHLE5Y/vAgnxQz37MS1bul6j+vXtKDtqra+uRjXlv1Zc/8a6FfgAClgXw +1438614076.950,1.000,0.000,HISTggAAANJ42u1XQQ4CIQykLXTltB/wL37Lq4k/8KM+wYOsh0kmRY2amM5lAgydwkLD7s+XUynrsdxhg2WwHq4lkUgkEolE4h8gP/Z5vK+e1Ec+QvQ2qa+gU8KV5G+EFdq7wY2MF+Kz5blAHId+h/gV9AtwA15IG+d30Dnk6yR+h7wa2Z/NZyV5OcSzYB0VdE72v8N8Jf8HPjkenQsh312CcyDEv5Fza8QP42GeNbhfSvajBOvVyfuoJO5svwV1RIPxGqxbXqxj9mZd/lQdly/5hPX8BhUYBV8= +1438614077.950,1.000,0.001,HISTggAAAM542u1V2w3CMAy046TlUcQC7MJsSGzAcozBCHwQ/5x0cigI8eH7OfnsxHbipqfr7SJyvMsL1lk7l/NDEolEIpFIJBLvQ3+0ToltREcuwEpsAb2SeM/biF8gDuucO09gV7AbMFuH+mZwvcdtwe/1HkD3+B3YzO/9LKQOzOfxe9AX8Buc90xYod+J3G8l94bnhPsZmS/sp5G4EsRZsG8h81/JfOvgvBvJV4P6JMgjg7qS74/5hfTTBvuP9LXvk3yoRyhffl/X1vF3/6UnbRAF1w== +1438614078.950,1.000,0.001,HISTggAAANN42u1WSQ7CMAy0szWlSPAA/sLbKvUHfJQncCDhMNLIbaGcPJeR47GdOItyWx6zyLXKG7GxNg73pzgcDofD4XA4vofu1H/+ZcBb45ORT+E/KOCPJG/nDDqM61zAj3aCfBXsBHHdHkidQsbPEFfBPza+kPER4icY7/oTmT+us5A+F9BNRM/6jP3JYA+gw31Lxj4nUp/lCQbnlXWt+8DOvZDzHkk+1gclOiX5lNwrIfeS6ZX0Nxjz043+aNTfi3DQu6k/enePftdl5Xn49zpo3RdEEwT1 +1438614079.950,1.000,0.000,HISTggAAANZ42u1W2w3CMAyM7TipAMEC7MJa/CKxAYsyAh+En0OnhKBKIPl+Tm6cq+NH2uP1dknpcE5PWGNprKd7CgQCgUAgEPgFyJ/FKV+eQybPzfwNbP3wfdph9Mtgv/1nwnODfZnoeePauMD+Daw76FTgDDoF/HeNF7AL2Bn8XvFswXbwW0BvD3pGdAvolU6eKsm7Ez0n+TWSJyN1c1IHzBfrPyV9k8k6opJ9AnGN9qcRvdyJ24iukjnVwbll88/mlMXT82d5lsH66Ur35Nrfk9n7dzZ+eQAE7wVu +1438614080.950,1.000,0.001,HISTggAAAM142u1W2wnDMAy0HrZTSqEDdJeulBUK3aCLZoR81Pm5ctgpFFLQ/YhTcrIULsK35+uR0nVOb1iL0qLelxQIBAKBQCAQOD6EcNmpl516B66gM/L8494J+dxiIX1k0G2oLU7AC+SnTl5BfwLu0C+eU0DH+DbHGXglfTrwSuo70Vc47wLnGuiccJwf80reY/5C3ziZG32lxI9GfNXzH+u7DP5fWN+IX9FfSupkMn/q9N/73rkzx+g+0MF9pIN7yL7cW/++r48+36/7kBWd9gVo +1438614081.950,1.000,0.001,HISTggAAAM142u1VOQ7CMBDcwzgJpOAB/IW3IfEDPkpJSYHTjDRaQLFA0U4z8s6esWOfrreLyPEhL3hjbWznuyQSiUQikUj0gAL/W19b/d6f6mg3WHtgV9AZF7IukKe8qe+IH/br4L+sx8aHxhV4hLxD4wn8Bsg/Q/xi34PO6hTQHbhC3onM56CXYM5K6sxkDiX7ivsipC+FOtG+KuhG8imJM3LePThfRuphHPtPjPThQX4j8Rr0IaS+fXlPrO2/9n2sneN+PV/vd2Kr7yGd/wk3iAXe +1438614082.950,1.000,0.001,HISTggAAAM942u1WyQ3CQAy0vVeABymAXmiDdpDSAY1SAg+yn5FGjgJBCvJ8LDu+1+vsZXrcRcabvJFmqjO161MCgUAgEAgE/gG6s/z0Q7218T3aYfhuJHwGaiRuge8V3qedP4BeAbmAvMdt4K+AvPsfSDz0cwS9BvYjyE+gnyEPrKPzZ5JvI3EroXlhP7Bv1eknngOeWwI7Jbw3Txif+U9gb2T+kuMP61Iynwp2QupT536Yc7/N0RNSly3MQ4mftXvGvrSHt9p3v9rne/vvbN6PF+2gBVg= +1438614083.950,1.000,0.001,HISTggAAANB42u1VQQ7DIAwDJy1dd9kD9pe9bdJ+sI/uCbvAxZIFrdhO8SWCOomDwL2/3s+UbqnCasw14vFJgUAgEAgEAkeQY86/1MkizuoDyls7PBN62nenvEXsO8UieCbqFNJ7JV7b3ykPxOM6RfRt+xtFp/4b9VmJd6FotPZOX9Ba6Vfz7bTm81hEvol7wucEcS8yzZfFvMw30Ue9B65nQg869zgJvazLRR8TdSDOHYPvHeI9uuCf9Q8M+tGob6HjU0fr/dp/z/rs7P/iLL+Xer8zzwTw +1438614084.950,1.000,0.001,HISTggAAANZ42u1V2w3DIAzE2Dya/nSA7tLZInWDLtHxOkI/mvycdIKohY/K93PCOZsjYLjeH2sIl2f4QDeWjePtFRwOh8PhcDj+GTJYf7SuTFpPBJZOX0rikXxXUj8RXYLxXjdvbJBXQF9AlyC/QHznBca77gRjjGfCZ9BVUp+xQR6ubyF+EuQz3xU4E3+V7Avbj0j0uL8R8kPjPBg5t+y8RlJPSR1tcKs/jfhiflhfGZnfSP9K57zaqbdG32rDR+s+ksn3o3x5Tx6dXwb5HfUOyY//0/T3+w3UdwWu +1438614085.950,1.000,0.001,HISTggAAANV42u2XawoCMQyEk3S77eIV9CyeTfAGXsAjegR/WBEGhrguCOJ8f0IfyaTpA7o/X05mh6s9KMP6sHG8mRBCCCGEeOFf1ouVuv5hv5N5rB2kPQ1bwa9AfwG/CvEK+M8Qfya6BdoN7AL+HXQ75NMTPSd5ddCrJP4CFvPtyfpZnk+7g3mVxGlJfdG/kTrjPuB+BBlnda3kn8LOoSfnAfMIMl7JfH8zD0vuCYtTiP+U1HPt/Wd52cr7n+nGxnc2Nr57/0Yk77nQORJCCCGEEEL82D/lDqqmBZA= +1438614086.950,1.000,0.001,HISTggAAANV42u1W2w3CMAz0iwYqhDoAu3Q2JDZgUUbggxSkk06uaPnz/ZwcO37VSXq9P24i0yRveGftbPNTCoVCoVAoFApfKLAQ+V/wlXlpsv/zvwec+WN+A2QD+wOwEb+t8wB2jegV6jLQD8m+RT4CO+Sb+TtDH9DvCOsXYofxFvlE8grQN1hHZvUY6R/uG8l3wXwdOEg8nAcncxGJ3shcBbFj58jIPCk5J07iG+mDJufNkrpipd6SvmX3iCR17n0P2k73oyXr+mOeutFu6/uQ5b1X/2j8F6tWBQg= +1438614087.950,1.000,0.001,HISTggAAANJ42u1WSQ7CMAy0nY1UIPEA/tIr30LiB3yUJ3CguYw0ciWKKJLnYjkeL3HjpJf74yZyvsobaZG6SJufEggEAoFAIPDP0A/tv6pTN+IZsdtKPuMp/D8K6CgL6JnohcQf9gOsV1gf/h38sE/oN3gnp/4J+BV4DfQJ8hjwGsQ/Qv0N6mL76yR/B8n6nUi/MtRdiSyEbxDfnHNQyXksZN3IPlh+Ni/4/ZT0Izl8If6MbyvnjM2pOnOnRM8r7z/WP68+/dJ96N1rW78Pe4+/+/f3BYOPBUY= +1438614088.950,1.000,0.001,HISTggAAAMx42u1WwQ2DMAy0nZAE+HSAPrpJZ6vUDViUEfpo+Jx0Mg+o2sr3OTk5G2MHh+tzeYhcbvJG6qyd7b5KIBAIBAKBwDdD/yRfIzr9UD3weebkYUQvO+OgfvsPzWBvPACbo2uwXkicDDx3HjtX0FfYb44+w34hdgN7Am4QtxAewY/lg3aFODPUOZM6Yt8msI2cH9ZnXBfS70z6zvzMObeJnEfvu1TiNzjvj/7JscXJ28tLSb336ozU46h5pSfNVT1oXp49j/XH7jV9AdRTBQ4= +1438614089.950,1.000,0.001,HISTggAAAM542u1UyQ3CQAy0vd4j8KEAeqGFtIREBzSaEvJgXyONNpGQgOD5WL6P9fr6eN5FLrO8kDrVTu22SCAQCAQCgf+CHiy/fklfn5oP6x/5BBTtjOgT6I3EwzgZ5N5pAb88iFOJnwPfSLxE+jgTvYPdROpooG9Asb8K9gX0BewSyAv4nwb9Z5IH5Q7yicwlg30FuZH52WAPnewx21O2d07qNeKP72sk7tb/Nfo/Snghc7CddengLhiZh5O7Yhvvq+6s6113/dfuvR68Pl0BppIFMQ== +1438614090.950,1.000,0.001,HISTggAAANB42u1XQQ7CMAxr0nTthpB4AH/hbUgcdt9HeQIHOiEZWamQgEt8sbo5aZu5qXa+bdeUTmt6IneWznq5p0AgEAgEAoHAC/Lj/OLoGHt6hBI20BnJZxDH5sW8mcShrpDxzhXy7boJuIBugecK4wV47nzs3Mg8BlzhfSXzzDA2su5C8jbQZVLnQr7bAdbRgAXisa7NqQPz0+T4ohBf6KB/hfzv5EGfqnNePL+zc61OfbKzP9QlJ360z4zuTwb7jHzY5xjsT/057rvv5nnzxQPeugV+ +1438614091.950,1.000,0.001,HISTggAAANN42u2WSw7CMAxEEyf9ABXiAByEHWdD4gZclCOwoN2M9JT0I1jUs7HGGjuOGye9Pl+PEC638EUabRyt3d/B4XA4HA7HPhA31u2tL1v1lezSdU3+82r1xJP4DfypEDfxFvI1Yid/Bl2WvM1MXQe8lzjNc5Z9HGF/nfBBuPoHyXMS3UHq03p70SXh1IcE9Wfog+qzxCX4fhF0LZx7g/UoD+kycDo3oRAfK+fHCvNOc0b9jaAnHiC+9j4iGOjiQr+trOdf79Ta+9pm+n/1Ls3O+wFNsQU/ +1438614092.950,1.000,0.001,HISTggAAAMp42u1XSQ7CMAz0kkBDJdQH8BfehsQP+lGewIHkMtLIKZyQPJdRbNdb7S635/4Q2Tb5wDtrZ7u/JJFIJBKJRGIGmi34qV/6ZV+V2NlB/xb4RTuUj+/ISvQFuAKXgB3i4/lE4heSn4P+0rl1XjpfwX7EWUk9DewYN8LYj1HfmcRHvwb5r8RvBb8OeaB/dh3KMY9K5qYQvZF5w/sogZ2ReTfy34P74YQtsMP6BM4asAf5KfQt2mOf3GOZfD7owb5KIM/313/Uo2+5qAUC +1438614093.950,1.000,0.001,HISTggAAANl42u2Wyw0CMQxE7TjJLpwogF5og3aQ6IBGKYEDy2Wkp0T8Tp6LFSeesRXHu8fr7WJ2ONsTsVnfbDndLZFIJBKJRCIxDx+s3+VRFFi7/s/BeTqnul38DeJDeCrsq34V3io8HXi72JB1FX+F+JfubrN78QforKC3CE+F/SK6i/CuwlNEf5E8G+gF6Gr9HXj1PtogPqCvKI8G+RbgaaDj0Cc2uMdR/2k89XlM1j963w48VK/DHIjBO6R3aoO5YpN52OQ+zbNP5+e34/3LOj7p9z/X+/Pv4wOb4QVJ +1438614094.950,1.000,0.001,HISTggAAANF42u1VwQ3CMAx0HCdtQEgMwC5dhVWQ2IBFGYEH5XPSyUkRQgLfx4qvvjhXNz1dbxeR41meyGtMa9TlLoFAIBAIBH4D6Uv6Kax/y0+MCrG3Pjv8aF4JnwmvhMd8JXwBfiL8BNGI/kz0ZqiroF9Az5x9E9nnAPk91DXIv57bwbpBP4X00WBtcF48H/KZ+GLwHtAvJXPD5jITXTYveXBuK6yN6LF5Nadv6Ty/971Jp39K/GL13r1sTp06cev9Io5PXr1++B6WwX7+5b+/ua8HQKwFTQ== +1438614095.950,1.000,0.001,HISTggAAAMt42u1WyQ0CMQz0FZIVPCiAXqgNiQ5olBL4ZD8jjZIVl0Cej+VksMde493T9XYROUqHd6vd2vkuiUQikUgkEp+E/lkd9qJ4Ntkn3cizga8kjsH3YwDPCc8JL8Av5H4938F9Bd563+B3C/BRVwNeI/kwTiO2kLoW0BtwbnB+gDh7oo/pqKSfrG8V9DjpvxG9BayRPGwulcxLAB99J/MXk/Nsgzke6RTy3Nn/gflG6pJBHbpxP/jkntE378ln99qvvTf0y3lH+fUBXCcE6A== +1438614096.950,1.000,0.001,HISTggAAANB42u1VSQ7CMAyM7SxlOfAAHtEf8DYkfsBHeQIH0stIIxc1SBXyXEZ1nPGSxL0+nveULnP6wDpLZ729UiAQCAQCgcA/Q3aehzrrQnh0XCVxmN0IL/6Z+Cvw1LnBvgx+RurJoFNAr3Y+gM6yfgT7ifhViLfonUn+BfSbk9cEbKDXiF9z+o/x8LvAPlYH6lZyTkLOTQkbOV9x9DK5Bwp2I3Wwe4l5FPJu8pf33Ht3dWNf1s4Ldd41mx+2cj6lQX5b5/konb38f+THebn9egMb2AUd +1438614097.950,1.000,0.000,HISTggAAAMV42u2WQQ7CMAwEkzhpaS58gL9w5VuV+AEf5QkcSIU00soFgcTBe7Fc27vbNIl6ut7WlI6X9ISNmEcs53sKBAKBQCAQCLyQf1xX/Zwrom4f6hfMk3/LKyL1qviv5HMTvPQxjzihb0Ykf0MkzwE5+dV8g86C+pZ36CzQKaKviz76ot/Jef8qdJpYR5Wb0LOd6zVhX3Tn+1en7u1X77wkMad4FZ/ym4V/dS7N0VN5Fr736mRnnejH3rzPPL5/uZ+/rvcAbYoFRQ== +1438614098.950,1.000,0.001,HISTggAAANp42u1Xyw7CMAyLm+zBBBIfwL/wbUgcuPOjfAIHuoslqytCwCG+WM2c1JnSajtd7xez481e8MqoXM4PSyQSiUQikUhogNjE2jp1vft5Q4eNvtDZl4t6a3ygdVC8CF3h71KhmyqPpOM4KL7WmUkXtF4ob6Hnu8oHijNPwvdCfvb0PERfMzH3w30OIh4i34UuxHsNyhuFf67Dc+biv8SEzhp1vTHX3pizQvsU4SfE3I4iv2w8d+wHjfMF4QuNc2XCJzr9ovP+e/ce/NU936vDn/j6dD6+1A+e4OsFfw== +1438614099.950,1.000,0.001,HISTggAAAM942u1VSQ7CMAz0kqQE6IE7f+EbfAeJH/BRnsCBchlp5CDKoZLnMnJqj5PYTs/3x03kdJU3fGFd2C5PSSQSiUQikUj8Dl1Jx7/UVxJvg3FK/ApZV9D3YN0IV8LTwg3sAraRuE50GtF10P98n8E+gl+FfJ3s8wA6Few92OiP+3PgHeSv5H4MdDrUbSL9Nwd1Zftk/eUkTyHnbEF+1p+N9KWQeLQL8TfSBx6cU4N59cH5aoPzbsG7ooMs5B6Yjq38Luqf3tmt/RcSG6n3Cx96BU8= +1438614100.950,1.000,0.000,HISTggAAANh42u2WzQ3CMAyFkzikpfx0AXZhHdZAYgMWZQQONJcPPSWt4OZ3sZ5jxy9NbPXyeN5DmG/hA1tsXGy6voLD4XA4HA6H43+IsGp9KydSZ51WXuUZnP4deBZ5Bf+jhnxDXuWDqBOw70nEGeIm8AKdNX5c7BH7Feg6C501fw9b4w6oN6HOCF3krFca90LduXEPCX7uNzTu1RrviDoT3qnSybgs/EX4o3jPjFPnUf1gon+iiIui79in1qiXVs6N3nmSOudG3DgP187JsHG9d/6mH53v6zu+AdrrBWg= +1438614101.950,1.000,0.001,HISTggAAANl42u1VwQ3CMAyM3TjQ8mABdmG2SmzAoozAg+Rz0smOoBIP3+dk52Inbu3cHs+9lOurfLB0ls56HyuJRCKRSCQSx0Am/Ufl+7f7D2hwv4AebXXiC+ElmK+AvoJtnRv4Ua9BHvtWiGtO3sEnck8junG+DfwbrK9k3/CfiQ7PdwF7JdwgrkHeGqw72kbqXonOSP0b1LkRPfvvhHz3RnRC7oXfQ5w+FKcPF6cfsG+qo1eSv5I+9eYB61tz4mhw/ojD3jzT4DyU4LyUSf3sHP5V3G/fn8PexTcLZQXe +1438614102.950,1.000,0.001,HISTggAAANR42u1X2w3CMAz0I0lRQGIBdmE2JDZgCcZjBD5Ifk46mZafIvl+TnGcc3JNXfVyf9xEzk/5wAfrYLu+JJFIJBKJxH9B04Jd+6kb42vXeZBvwFEdC3iiQNyDeg5cgGd8GVxhHuOTD4MbqVvJ/NTrML/AGOsZ0Z18hH1ViHeIox+o10neCfIc6lSyD/QB/Uff2bmdrDPyv6HkXjp5bniPGty/Fugp8Q3PoSTfgjHT90CX+aDB+xmtYzol6CMa9I2t/UqCfvBrX9Uv+6OsPP9uv09vw+EFiw== +1438614103.950,1.000,0.000,HISTggAAAM142u2XTQ4CIQyFS2EGfxK9gHfxbCbewI3H9AguHGbxJS/gbFzYtyFMX19LKYS53B83s/PTPsjLmJbRry8LBAKBQCAQCGxH+rHO+q4TOqkzOt6J1CmC78I/w68inxm8Nt/he4W9CF6LP4HvQr9Cj3lUoTfBzjxOsO/Bm8W6MvhV8Kuoc9M/Is+C+QFxaKdfEXVmPbLYJxd6Jvap18ez6EsX/znsPx/07/V7L+7Wc6jOP+Mb4lI/D55zE7rWWf/o/fXtepP9N9a6vAHXdgWX +1438614104.950,1.000,0.001,HISTggAAAMx42u1WwQ3DIAy0wRCaNlIG6C6drVI36KIdoY/C56QTQY3USPV9TgSfMScMuT6ed5F1lQ9iZa0cbi9xOBwOh8Ph+Aa6c9y/+zXqpw7qAvkv1I35m95IXiVjA46gj6A7EV2qfAZdBp5gfob8La5AHPIEcYXkK1D3BfQZ5hsvsD9WfwI2WDcRf2fi80LqzsT3TPwJZGzkPDC2zvlj6xl8Z+dRO33G8mlnfxifNu5bO3FK+kdI//T6NRKfRu8T3em+O8p7oAfL8zMf3lg1BO8= +1438614105.950,1.000,0.001,HISTggAAAM942u2WUQoCMQxEk3S7u7qCF/Aunk3wBl7II3kEP1x/HgytCCKa+RmStJO0DW0P58vJbH+1B8rKvnIcb5ZIJBKJROK34A37V9b17/VG57iKf2CrTwI2/U8exf9yAHMe/QU8Q78Ke4LeFuPoX8ATeIN51GEdk4jTP6MO6ozIv8BW+71DvoB+iHw8L7WvBf4qzi2E3iD6p3T2A+Mm+ngQ52CNfmbfc19qY50huIh6XdTVilunnnWus8XRyO8i7i/ee/6h+/Zb341332m/Aw1PBaw= +1438614106.950,1.000,0.001,HISTggAAAM942u2Uyw0CMQxE43iTrBBIFEABdLG1IdEBjVICB3YvIz3t/+a5jCKPP3FiP96fV0r3Z/rDR7aR8/BNgUAgEAgEAoH9sI06m9GRPQsvrcPFzyCeskP+yb9C/Akd2MvITXRFdJ1wE3axVzn3wlfRVYl3EVZ/1TWpW/MWOZPd4V6a9wZxDeKVmb642KmeCu/p4GegM/gfPfwPh/7SXHQwF3P/nurLcB+aQ4c5zgvzUn/Tyrm1lfsqb9xXR+29pfvvqP1sO/3P8jsrzuq8P+trBSc= +1438614107.950,1.000,0.001,HISTggAAAMt42u2WQQ7CQAhFocw448LEA3gXz2bSG3gBj+gRXNi6eAlBa4zW8DekwMCHIXQO4/kksr/IHTZJneRwvEoikUgkEonEL0L/hNfj3bUwvj4ZV9/kJ4i3wfuReaiP/PhdIGd9Q34v33yuw7/Br0NW+FXIAvsW8WtgJ/8d9JQdcciHfTEnr3dfzfHrTt20mzNvvDdz8otTjzc/xcmrwdyTlwRxLKhDF/KUgC95l6DPEpyP+hHthSGo49U9pR/ao9/a87ryOtb639UbsUgFhg== +1438614108.950,1.000,0.001,HISTggAAAMl42u2WwQ3CMAxF4zhtU5AQA7ALsyGxAYt2hB4olyd9LJB6APlfrHzbP4mdNL3cH7dSzr084Zu1zdbrUhKJRCKRSPwGLEuQfX/jr+D53/dpHP2O/BF2EPFNzNuDfM7TodeQN4F38B1j8gPsS2cWeY71OeJPYv1H6NN/EPudRT1H6LIO1JnQZxd5TfSxizx1XiwYV/BN8BboNXF+K6wHfhP3y0R9ShBvQV3U/VbrNNEXC3Qt2Oe38OB7FfVjr/fNdtL9t/fcViKtBPc= +1438614109.950,1.000,0.001,HISTggAAAMN42u1WSQrCQBDsZTJD1IMP8CHe8jbBH/jRPMGDE4SComMCItJ1KWqmt/RsudwfN5HzVV7wztrZplkSiUQikUgkEm/oj9fDtG60N2A2vvxHDqCZXSHjCzcSt3Yewb8QuwIa+UTiHkA3Mt+IrqAb1Id+Htg7WYch6D/GOxI77JcTZvWw9bZgnXEfNDKOLEFdhexnJ7qSeV35PdE5wb4K8Rfij3628pxqkM+DvFvvP9t5n+qHefXL96vujPOv75g9ARLUBSo= +1438614110.950,1.000,0.000,HISTggAAAMh42u1XwQnDMAy0bCtuGkoX6C6ZrdAN+umYHaGPWp+DQ04J6Uf3EeLk02EZkdwez3tK11f6ovQoPeb1nQKBQCAQCAQCx0EcPu+sh7x9D2qPFeoEfGSoN74BbzoT1Ft+InWNnDOfM9RVR0dJP+MvwDfIM+Tq9Dv3uAC/gH8lfZnvAn7mjfeXYV6VzE+dc/geCpk7yzN516gzkb6V6LbB/onoCvs/cnQS8VccXXHuVwb9CtkXOnh+r73z6z46ep/+299WX/IBCBgFig== +1438614111.950,1.000,0.001,HISTggAAANV42u2WUQ6CQAxEt8susGriBbwLZzPxBl7OY3gEP4Sfl0xKQOOHnZ8Gtp0py9DlcrtfUzo/0hvdHG2OeXqmQCAQCAQCgX+E/ZhX5eWVPFn93zk65tR1iAXrzOtFXQ/+Cr6CvNMcG3QK6pc4oL5CvyGP+Q39jdAbkc/6I3SzqFP9L3kHsS8jdLjPg+A18HTi/Tenvyp0yNMj0r8mfJfFtfLZAH3Pn/RPEn6v4vmK8HVyvjNbed/ryxy9vfMnb5xrtlJn6/re+bl1rn/rPPi0jruvLyaeBb0= +1438614112.950,1.000,0.001,HISTggAAAMl42u1WWwoCMQzMo92tCOoBvItnE7yBF/UIftj8DAxdKoJo5mdIk0zT0M32fLtfRU5NXvDO2tkuD0kkEolEIpGYgX44/l/7ocTWjXnoNxJnxB/vxUrsyC9EbwG/w3og3qdr5z3ENdh3JXm4XyH+Rjj0D1DvsfMO4ozoV7Cd1I95eH7sbyXnxHrQbkSvkH4ukO9gY/7ovhjpj5N7U8k9LoRtwJWs++A7M7C3xpdBvBD/7BwQUufs3Brpz861d/W+db7/yv9Mn1zTBPU= +1438614113.950,1.000,0.001,HISTggAAAMR42u1WwQ3CMAy047ppyqcDsAuzIbEBizICDwyPk05uJRAf3+fk+HxxIjfq+Xa/imybvGDBGtwuDykUCoVCoVAo5NCD63vzv+7z898HjDojehZPwXOwEx9P6gf4YGywPsD3Ha/BPXgBH4xPUDdANwF30HWyL57DwGcl53PQLZBvkG/gM5N7xf7xXEbmw8h+Svo3otNEh/5C5tLJPFoSt4PzLaR/T+qz7w592D7s3pg+6zt7F/bqMrQ/vX9avt/p6wmzbQTv +1438614114.950,1.000,0.000,HISTggAAAMJ42u1WuQ3DMAzkJ0tJgEyQXTKbgWyQJmNmhBSxmgMOVOGS1xCiT0eKogk9Xu9d5P6RP/ywelh7fqVQKBQKhUKhcB408dtJerbIN/ArexeCvxH+BusBvAZ6AXzUn3od+B14uH/yrrCe/AvYqXcD/kbiBPEP0G1EB+Ox89miDvJG4sf8Gqkbq6sn94r3q0SP8YTsi6SPg/SRkf7sJC/2H2BcJ/kq+e7JOYXUMYuTzYMg+3RxztjiHGJ6vphnFs9+IJIFdw== +1438614115.950,1.000,0.001,HISTggAAANN42u1XSQrDMAy0lthOoNBjD/1L3lboD/rRPqGHxpeBQaXLIVRzEZY0I9nGdnK+3i6lHE/lCdusbFbXe0kkEolEIrFPSC7BrtZdAr4GdaK6GtQb8Yn4PdDDvMGvRH/YDmMF3tBrkNcg7kQX+1o2ewB/BV2sP4M+5nfwYx8LxCt8f6P+TPI6qedk34ysUyX9esCzIC7kvwJ5SvrF/VIynwZ8D/hO/Bb0IcF58UDHiR5igrG9eF8wXXvznmDnO+Kz+0u/dC+WD+fzq/fx395ZeQBwygT9 +1438614116.950,1.000,0.001,HISTggAAAMt42u1XSw4CMQgFWsaOceEBvIvbuZaJN/CiHsGF04UveaHpSg1vQ/gUKExp53J/3ETOm7xRdqo7tetTEolEIpFIJL4ZmiX4fL8FdkbsbdBf553wFd6VnT8AX2F9AX4F+QJxFPw6sVvA7kTyw3jHQX9d3ogeKcZtoG9APYjn4LeAHuu/kvpjPzG/Qvqn5D8CzyXqnfTTiF8j/ir5PoX01wjF/dRgX0rqpiSv6BxqcK41kI/OQQvso3kwO3dn7XVy/a/fL/9yr+kLJmQFGw== +1438614117.950,1.000,0.001,HISTggAAANV42u1WQQ4CIQyElgWVxOjdv/g2E3/g53yGT/AgXsZMCkYNMZ1LA21nKNvt7uF8OYWwu4YHtNnYrBxvweFwOBwOh+MfEH/M82k95Hv5bwPL8pXoqBHPdAT8GdYJeATiBOI2zRawGPfMryR+aXZN/JX497BW0F0RnQLnypCfIV5J3hb8C+hXwpuM/UT0I+xnqFON5yCdfcH8SviSwaOkzwXqQb7Y+d4U0rdCeJLBx/QZTyB6YXCtxj1ac2Z0zsUvzcF39Wf9jozyz1JXnPTeus9xB1dDBc8= +1438614118.950,1.000,0.000,HISTggAAAM942u1WSw4CMQgthdqqk7h1MXfxbCbewIt6BBe2m5e80Mm4MJG3IdBHKZR+1sfzntLlmj7QLqXLfHulQCAQCAQC/wmJEvwE8kaekP2TyflRL/g/7PIA9gb8wTP4Zw6/M+gKfINxjNuI39BPhH904lTgL2QenK9AXhXsBexGxhdSR8wf/Q3WhbwK9ank/99IfTw/IX2BeRToLyN5ol1Jn2fSB8nJQ0l/4TrVOV9ePOank+dTJu9j754QwhMis3OPbH0fZKe+N/633jd5A7Y5BRA= +1438614119.950,1.000,0.001,HISTggAAAMx42u1W2w3CMAy0nRdtQSzQXZgNiQ1YhrEYgQ/Sn5NOjlR+AN+PFTv2neM27Xq7X0XOD3kjdavd2uUpgUAgEAgEvhMaOn5ybixuO+saqaPgz91W+H8sEG+QV8BueXO3B/AnyK+QX4GvkPgC6wn0HUEH9jmRus3hX6CvBn1t6xPwZLJvJvyV1MfzG/Un0KGkzwzWyLx1UIc6z6kSHnPOLZF9mfCiTiH1EYXUU4dHiE4jemzwfVWiW508b23OfPRD99ze+/Bfvzv6Ap5jBac= +1438614120.950,1.000,0.001,HISTggAAAMx42u1WSQ7CQAxLwrQdKpB4AH/p25D4AReeyRM40LlYstwigQqKL1Y8cWbpLD1fbxez091e2M3sM8f0sEQikUgkEoktwv98/PFm3SD9BMnD/z8H3UTc8jvQO+AC7YW0o94TX+MK+QPxN30P3PQjxAeIA/SR9Fuh3gh6L+aBeWpeVazXAN8J512IDzkW7gPlC2DcNz3Zj6yOkXpB1tmJT50TJ3ER5yoIs/vAyThVewifrezXV95fLsax1P+pe/9b78Wvv0tbXx9/AsTABY4= +1438614121.950,1.000,0.001,HISTggAAAMB42u1UOQ4CMQwcO1l2Q7Mf4C+8DYkf8ElKnkBBthlp5IgFROFpRr5iZ3KcrrcLsN7xQulsnf38QCKRSCQSiUTif+DERnEb9LuIF8Gqbw3qNm6ibuq8kH8WeZt9IOY87sv5C9nc70jxJvwr9XWyud8kWM3biHmdmdiCOOvYyFY61uD+8HlD5JdgHbb5vF3UK38N5ndxj9UcJmwE78mFPgj0ssF3jTf/hTKofzQvAj1+DdtZZx/aj+3s/3WdnpDOBc4= +1438614122.950,1.000,0.001,HISTggAAANJ42u1WQQ4CIQykBVlw9eDdv+jXTPyBH/UJHiyXSSYQXTVxO5fJlLSFUkiP19slhMM5PBGNxVhP9+BwOBwOh+M7EC/BS3WQldyHEC1v1k0JNySSR0EnMk82+0TWZ+NsXI03YM9gT+BfIQ/mRXuLszfegm5xC/AOzoH7wzwF7Kgz4Qo6gb2Q9ZnUO8K5A9zj1PFT8I+kb7AuzC91+k47fR5JP7H3oKSfI8nH9iUkDtPaeb+juvfuR88/+m8stb9P/aeysJ/8KM6/zhVrn2fkAcB9BUA= +1438614123.950,1.000,0.001,HISTggAAANF42u1WyQ3CQAzc8R5AeIQCKIAuqA2JDmiUEniw+xlp5ATCB3k+ltfjYx3Hyfn+uKV0uqQ3cpfo0q7PFAgEAoFAIBD4HFhox8Z5jP7vMp2Dzgeq4Kl4LBvpk+PfKG8jfnH4ELxh35N9+O+Iz7xZ9GMi3tAPXR7Jj+uYSW+kG9XXKL66B/tz3kr+fC/V90LzUcRzqOJeJuYJYi6LmDtz5hjOPED0x8ubRZzs1KXievWb817D2R9Y6WcL909euYew8vzbPbr1Xv6378zP8r4AQFkFJg== +1438614124.950,1.000,0.001,HISTggAAAM542u1USw4CIQylpQPoxHgB7+LZTLyBF/UILpxunnkBkwku7Ns09PugpZf745bS+ZneyJuUTerVLYFAIBAIBAL/AflR/Ky60vFXsGcS7/oFzgZ5jMS5bETvskB8AbvXWUkcSgPeJ+BxhDPTHyBPg/yN1HVZIR7tK8QzPyP3VNKHhcRhPyt550reOXf6g3xx3grordN/Np9K5hjnVwkfxhvP+F7a+Xd58H8hTyP52f3yoJ8Qvx7f0Tzy5b6SnfaZTN67e+3p2Tw+6r8A9MUF1Q== +1438614125.950,1.000,0.001,HISTggAAANh42u1W2w3CMAz0IyUtAsEA/WATZkNiAxZlBD5ofk46WWkFQsL3Yzmuz/HFSjrfHzeR80Xe8MXqYu36lEQikUgkEv8JTQm6dNGNepb2/0XitnKfSngUfCd1jPBhXiHxCXwjdXZgG98AdUYSR9t4KuGpsL8T5B0h3tYPhLfCvkbIx/rN34NvsO5QfyI6FfL9EOjrRBclc4k647k76UeB34J59WDdg7nCuXfSD/L2zr0HPELOQQMrQX8W6MXuAw/qadC/rrz3dOP9KV/O+/T716uDyW9BX0b9BRE= +1438614126.950,1.000,0.001,HISTggAAAMt42u1WOQ4CMQy0ncMLouAB/IWvLRI/4KM8gYKkGWnkXSig8DRWHMfjK9m93B83kfMqb5QhdUi7PiWRSCQSiUQi8Tk0WH/rV+E/roCdBbwKdgJ+8HwlvEZ4534ncTewm2sPzjvoDeIzUpdpfxjySOTcPxE+BzuH+DtZV9A34GU8C9hjfgupZyG8DvoKfrCvLYi/kryxLx7kUci8Gomrkzm2QM9kJXPK/KLsQbwa+LGN91123usoH9nIb6RP+ufvpeysa+LH380XL1QFaA== +1438614127.950,1.000,0.001,HISTggAAANd42u1WwQ3CMAx07KQJhQcDMABbMBsSG7AoI/Ag+Rw6OapA8PB9TlXsy9WNnZ5u96vI8SwvWOfUWS8PCQQCgUAgEAj4SBvzdKN+IqwkzmDdHD+D86SOsv/JST0jcWvnXedG8hrEjfzSeQG9BrySvAJ+M+hVeFbwsQDvYd8KjD6yU9dK/KJPg/Xh7wB6C8SZw0rqhuvou5BzY8S3kfcQci6UfDcj9czET3HOc4V81nesj430H9vP02F95s0NI3VIH55fOulndv79en7Ll3392331pvMEEZcFLA== +1438614128.950,1.000,0.001,HISTggAAAMx42u1X2w3CMAz0IylQkGCA7sJsSGzAQozECHyQ/Jx0cgSqQML3c3Kccxw3cdvleruIHO/ygjfWxnZ+SCKRSCQSicQvQv80P8XvNTKOfhmcz+Ia4UjnMN7tDdg14K4vwD3Ojui2ZJ2um8Du/n3jQ+MZ4iGfwJ5JXhPJr5A6lUBfYHwidXKSdyXPEeujJA+FeAp+BxZyjtj+LdBpsF88J0b+e/zNdfGeV7BH40lwbyMYyceCuGwfPtjHLJinK/VF+7B/rq379vtJnzV4Ba0= +1438614129.950,1.000,0.001,HISTggAAAMd42u1WWw4CIRCbgQHcjR8ewAN4C89m4g28qEfwQ/xp0gw+NsFk+tMstJSFWZbj9XYROZzkidxZO6fzXQKBQCAQCARmhP5Zrm40DyXt6UsfssGzwv0RuZH7JeYa8Ku/gn9Hxl2ADfwV2tfOBcZrkFPAj/kGugX0BXSN5O5J3gr+0fEraTfiL2QfUJec/kb2tTp1xOpJnbrB+s7EnwdzjOiZTwZ13vsmMp/irIP3HauTKx/q3j2PGNKPdbOd+7P8JzZflwfRigUW +1438614130.950,1.000,0.001,HISTggAAAMt42u2W0Q3CMAxEncRtQAWxALswGxIbsATjMQIfpEJ60slF8On7OdU9n13XqXq+3a9mp4e90QaXwfXytEQikUgkEonE5//oX7oovwo/5U99CfTKt23sh3FHfkN8gr4Jvxl5HfoKv4brPXyY3+HvIv8gdOxrBq91dtCvvgv8V59j0N8i6nQwn4N9sN9JzJX3yQW+0XtxsRcu9o9+JuIe7GkVe10Fqzz2ZYHeRT0Te+/Budh6DouoV4O6tnFuag725XfGfownxnxeLnsFng== +1438614131.950,1.000,0.001,HISTggAAAM142u1Wyw3CMAyN7ZAmUg8MwC7MhsQGLMoIHNpcnvTk0PZQgd/F8ufZrd04vT1fj5Su77TAVimr1Hv3BAKBQCAQOCvk5PkCx/RdHV3ALsR+1PwV/h+zU/8CcRn4nVfAjryuV9AV4gr4jdRtENf1CWQl/AK8RniN8Lt/JvEV6mTSpwp9aKR/RubXIK8SHs5PwD85c2Hfjzl5bdCupK44cV486xvLZxvPqTjnkPnV4W2th++TBvfM3ufXL/fR6D6TnXvv3+9L+dG68gGdfwXM +1438614132.950,1.000,0.001,HISTggAAAMt42u1WQQoCMQxM0tStiuIDfIg33yb4Az/qEzzYvQwMqSIFIXMZSJNJtk3aPd8fN5HTRd4onbWzXZ+SSCQSiUQikZgH/dJug3FKWIiOkTz431gDP2QJdNZ4B78N8WP2BusHsFfgBbiA/xb0Gvjvgdf1Xecj2FG3gj7WsYB/I3VX2D8nOg46uO+sDmQPzpOdjxId7FcPmPVhgXxG+hO/uwZ9LME8lMF5tWAeozlmc21BXiVzOJpPAr1P7zuddI/Kj/Lp5Li/eb9eVDQFMg== +1438614133.950,1.000,0.001,HISTggAAANR42u1WSQ7CMAz0kjahXHgAf+FtlfgBF57JEzjQXEYauVCJRXguI7uJJ04dJ8fzZRY5XOUBX1gXttNNEolEIpFIJP4B+mPrMzLOiM3m4/eIWbwCrOR9ifYQ+LveDsaNRLdzBXYyv+vswd/tCfwV7B6vEd0GOqjbwN9Ivg77gXkY0RnJ/y5kXiU8kXyV7Aeus4IOq69C6sJJHaK/kvoZSP3aynNhpI4lOCdGxluQvwfxJNhHX9kH1vZBfbLPMN7ah/XFvLb2+U/fD99+P70rD70D60MFeA== +1438614134.950,1.000,0.001,HISTggAAAMd42u1W2w3CMAz0oySpaCUGYJeuxApIbMCijMAH7c9JJ+ejQjx8P6ekd7ZjR23Pt/tV5HSRF3xlXdmWhyQSiUQikUjsCf3y+L/SP+vMo0SPdRzgf3IAVrJGvRH/BHkKPMf9LU6FPBV8BfwNGH1HkreBbiLnGMG36efgXA3WbH+G/ZH0uZL6jDD2q5G5G4nvwBL48d45WeN8lJyrBHGM3E927wupYwjqV8Ie6CSox4leyTyl09f7vngXbGfdv31f9EPz6BPevQVn +1438614135.950,1.000,0.001,HISTggAAANl42u1WwQ3CMAx04rQpAQnx7y7MhsQGLMcYjMCD9MGhk93Cg4fvYznnOM7JdTpfbxeR411e0G5Tt/n8kEAgEAgEAoF/QAoJ3nTw6pFJfCJxlu7MV8iD5w6w/vHfSfZhfO22AN9gfTlvAjtCngz8ieyvxG8kP+4/AL8Hv8G+Arwa91jid2CRr0TvauisRN+B1KPkPhP4hfSRkn4fSV1K+kWhXwdyn+Lsx2LwSupLzvyMz8b3bPFC9MjG9y/OOr1zOm+cd7JxPnnn59q5uvZdSl/W/bP34wlVtgXl +1438614136.950,1.000,0.001,HISTggAAAM142u1WMQ7DIAzEGAjp1LFD/9K3VeoP+tE+IUPocurJFkrbxbecbAw2Bzi5Pp73lM6XtEMHy+B8e6VAIBAIBAKBT5CQwKWHfEnXd1wh82Qyn+D/4OBKxhXmZ1JXAVaIV+BC1muDO9i4bgV/NbiBvQxegTG+Gf4V1kN/g312YKwTxxdSbzF0V6N+1K8TfU/kHLFuNc5ZnPF4L7Phr8Y98+ZhNnsPjYxX8u6Kcz+Z5PO+Y7StfuDVXQ7qb3myn8lBffXX30P5U97pfBtrbQUQ +1438614137.950,1.000,0.001,HISTggAAANd42u1W2w3CMAy0r3nw+GEAdmGFroTEBizKCHzQ/Jx0cgtCCMn3c0rinB3XbnK+3a9mp9lemBb2hXF5WCKRSCQSiffgmYLEDwBRhx7YO70HlQ6ELq8XYhBPYt+Yb8RDZ79wJYbgKuLqND4IfR4XocP2nXSLYD6nC3+N8tNpHuS/kd1EesdgffBOxAMRXxfM35f98XwVdcvxVVFPReTTA12IuoToDwT9ourcxLry60H/qH62lX3vwX/EgnNE9x82xrf1PvWV9vhwvwf5/9a74d/fE/4EjJcFLg== +1438614138.950,1.000,0.000,HISTggAAAMd42u1XwQ0CMQxL0rQFTogF2OXW4ovEBizKCDxoP5asVLzuEX+iuo6TttFJd3+9nyK3h/xQRtQRbf9IIpFIJBKJROJ4KIs6XYwsz0iM/EqQh/076CroKuGdxKlrJP88Ygd+rjfQGfhN/gLrE/SB9R3q4PoKeU76Q9+N+HpQtwFvcL+dvA/uFzh/J3PUgHfib8CXYK6c6KN5ZXVYf0L2K/ufInPvRCfERxd92bmUvJ8G35eorgbfIyP3JgGvi3n/QuWY0C/mZwVt +1438614139.950,1.000,0.001,HISTggAAANR42u1XwQ3DIAzExlBaVeoC3aWzReoGXayjZIQ8Sj8nnUyC2pfvYwG2DzvYkPvztaR0e6cPcpfSpT7WFAgEAoFAILAHEin4a37l4PcQZ12JvsK7MTvzjA/XL4TnBHqtywp6pcsrjCvwVZBfvTPw4XyFfRpI5EM7g33YIE8mcRfiH/kb+MNxIXELiTfBWEk+KvhpEE9xzo+y/xNyfszxY+S826C9EnslMhM7I/Xl1aWS+tHB+hfHvzj5YX1gtA/pzr40e6/Ij/zO9t3Zfn743tgALKIFuA== +1438614140.950,1.000,0.000,HISTggAAAL942u2VMQ7CMAxFbScNlRBiZeAunK0SN+CiHKFDw/Klp6iAEIP/YsW1v+3YTq/3x2J2vtiG0qV3GbenJRKJRCKRSPwCnvV95Odfys938hU5h+gDeAvIQ5eT2L/OtcsmfmofA371n0E2iFslboM8j1CX8jfRK7/6nSAu1ad5z/K9wn22Ae8E/hrPRB/QL+o39dXFroodzQXNqQG/7kMFPwdemvfYmZdDniNeG9yDD/ab9tfefC8c8vv3/4Ovo6kE5w== +1438614141.950,1.000,0.001,HISTggAAAMx42u2X0Q3CMAxEbSdtEEKiAzAEG3Q2JDZgUUbgg/TnpKcIED/g+zk1se9c103V0/V2MVvO9kTp7J1jvVsikUgkEonfgmcL/vK5+iAu3szf8mrnSdYLcBV2ydt0dp1nWW+yXiDuIDoN6p1lnzgkPkS/gl8Dv33nRe7jKHEV+jLJPvkVuG6D/VF/Z/BzqSvAh+ZG2WHeaI5o7rS+CfIMfE3iqB7KD9Cr8D9mg/eV+lPAJwb6DnoOz/VVXTpv/MPz7dvnaX43B/16AJC5BS4= +1438614142.950,1.000,0.001,HISTggAAAM942u2WQQ4CMQhFS6EdHTXxAB7CG3g2E2/gRT2CCzublxA0ZuJC/uYHCpQB2unpdr+WcjyXF3SwDK6XR0kkEolEIpH4BSS/86s4EnC0P/V1sDn6ynek8760wM/g18AKvwZ5A71B7oNn2B8Q37A+Dd5DXuJuwfRf/HbIg3Yz4hvsO+wV+3bIc+DXnbqxfmT2eXJYnH6z783ppwbzVZ25Mmf+1JnzCn17c/9ortWRo3NkQX41ON8a5P8pR/WK1mWle+/T+1RWvof/9X8kT6fQBTU= +1438614143.950,1.000,0.001,HISTggAAAMl42u2Xyw3CQAxE7f0kSxASBdBLakOiAxqlBA4klyeNNkQ5gOS5jHZjj8fKfpLb43k3uzb7IC/sC6f5ZYFAIBAIBAKB34HvzEsiX40zuAi9JL4jVx7AnGf+iHrMa5ivyDtBt4rxCD7DxwSu0M/w0aA3Ycx+G3jVv4j+S6deEu9L9Tt04pOIo58i/DGO66Wo/w+wCR8u1oeLdeyiDvtXPlz4rxv1smAX/ead+7e3r10837qfvz2f/KDzyzp+/CD9o8/dv79v3ip4BPo= +1438614144.950,1.000,0.001,HISTggAAAMp42u2VwQ0CMQwE7SQkh/hQAAXQBbUh0QGNUgIPcp+VRgkH4oP3s/LJ2XjPTnK63a9mx7O9kDt753R5WCAQCAQCgUDgc7jwt/S2IglTfRnyyBfpuehl+F467zpXiUlH11eJV15gnyI6Vfys8V7WNYkX4YPo6v4NWPUq5C2SnyA/S1zAdxaf6qdBH9qgDup7gfkpEmfQJzaY4zKox2HuqC46Rwn0bfK8GPxnA58+eb5tsn5/8z4brfNBf7ben2nS76/u97971577xwT6 +1438614145.950,1.000,0.001,HISTggAAANZ42u1WwQ3CMAy048QtlUDizwDdgtmQ2IBFGYEHyeekk4vgg+r7WInPl8Z20lzuj5vIeZU3rFvttlyfkkgkEolEIvEP0EzBpnzge08Jn/lLoIvxDfwN/MbeoWCHvxI9A753O3U7w3jEHYBfgTfB/NA5wnrox+9aiG4D/wLzuF8nPCNxFeZxXSf8ArwT2d9M6u+k3h7U30g/snyw/mwwZn2F/VyJbiNx7NwY4Wmgo6Qe7Jzah/rR+VbS1xLwovtENt4jJVjv1/e+fqmz9/+O7jwv+gJXJAUM +1438614146.950,1.000,0.001,HISTggAAANd42u1VQQ4CIQykLSxiPPgA/+KX9gsm/sCP+gQP4mWSSZHNJiZ2LpMWGFoo5XJ/3FI6r+kN6yyd9fpMgUAgEAgEAjOQP8tTB/OXL89JJs8V4/J0jPizkx9jI36FcdQvwEp0C6z/8AI2zqvOOLOPnQ8Q16lzA/0K61hcFXQqyafAPg3GMa4F9ArRZZxBF+NYyLka2M2pn0ruOcN8rA8jfqxbI8zqksXh1XFx+gGzxXmvMviu1ekH6tyDbewvOhiH1zfSxv44+//o5D57z/uVf3XvfeUFxDIFZA== +1438614147.950,1.000,0.001,HISTggAAANN42u1XSQrDMBCb8Zallzygf+m3ei30B/lontBD7YtAjAsOpWV0EWZsSR4cJ7k+94fIdpc3YmWtHG6HOBwOh8PhcPwC9M/zKBnrYJ1en0DqoZNT5UzqC9QLfK+2eROMM/AE+RZg1EFu82bI3eor+EWYt0HfEuiiXgY/3O9KfArpVwG/C/Fn6xPp/wy5MWcEvUTGgewH/0sS9DGSc2idOyW6bL0Y9UjyspyWvpJ10rkv6XwurT6we8XyFZL703rvfTpK51v3vJ6c4+z34vB+vgAutAVf +1438614148.950,1.000,0.001,HISTggAAAMZ42u1UOQ4DMQg04PV6t8kH8pe8LVJ+kI/mCSnW24w0AicpUjDNyDCAD8z18byXcnmVAzZYBuvt9CQSiUQikUgk/hkS1CnRC7AF9bg+9Z3YMT/WqcBt8ErWCvYF6qAdeSX1WH0D/0bydtB18Ddggzo76HfQ4f67c06MU2JvJE6dvsC4Su5rIe8kJM7jHuyjEuxHcc5Xib86fa1OfpZn9t8K0Wnw/7I54M0Zr65Mzi0L5v/VXPx23kpwv7PnkA/90/f0BmzHBcc= +1438614149.950,1.000,0.001,HISTggAAAMh42u1WyQ3DMAzT4SRKXx0gu2SFrlSgG3TRjtBHnQ8BQkkeBYqKHwIWJVkOHXh5PO8i15t84J21s60vKRQKhUKhUCj8PuygXuF9yOo4YdQbvjNBP3Qeia5BPDrPsO4QH6H+tj5BfIL8BhxJnY0vpC7uA+duoJ/JHNgHz431jeR82Dz4HYP0ZXOyeR3qol6BB+KbIH414tdG/Mj86sSPmZ+d7EfJfcvy9uYLOT9L7qkQvSR9Lcmzg/8bTeLZfs5Cv9zv36BvJa0FTQ== +1438614150.950,1.000,0.001,HISTggAAAMp42u1WwQ0CMQyL017bg88NwC7MhsQGLMoIPDg+lqycEOUVf6I2iZPUatXL/XEz2zZ7o+wWu/Xr0xKJRCKRSCQS8+H0D5sNBGvVFw72CfpfOq2r4C9kuY9G+Y3iOuUvgreRvxPvx66C50x5C9lBfrZN9Nkpf6W1i/lVnSHiTsTD5zLEeQyho9IZgc5V6FqFbiXQ10Vd9vvBvqvIi3g4z0T/HA+aqwbvA0QdBPcy4vGAF0JPC3TwL9+lX+3/+11V+z6pzqx58AL+yQTy +1438614151.950,1.000,0.001,HISTggAAANN42u1WwQ3DMAg0xrHjtFIX6C6ZrVI36GIdpSP00fA56YTVJn1xnxM2GIgB53p/3FK6PNMHurFsnNdXCgQCgUAgcAwkPkHc+w/1IDvHoSBnYGH/i8ROnHMy4UL0J9i3OCqsm9yInp0zg56td3KO6S/gbwGuwLZ+AvkMeXbwV4lcgGfIU8GukXw75D1BPEr84/13sq/OPXn1lcFOHH0l+4XUOfpvpJ7V8Vuc/vD6SUhfymD/qWOvg37U6dPkzAsZnG9eXEfPv2/n7L/j3Pv9kDdbugXI +1438614152.950,1.000,0.001,HISTggAAAMV42u1VOQ4CMQyMj40XQUFJwV94GxI/2I/uEygIzUgjU3CshKcZJY5nvInlPd+Wa2vHU3vABstgvaytUCgUCoVC4Z8gyfrd+r/SkYSV5Hmiq6BjRM9AD891iE+wz+Ls/AQ+M+H94MPggPwA7sSH1fn034FfwNrhHgP2g3zvTOJZfR3uX0l+J+/L6jDyvkJ8nfSjkf504q+JjpK+FNLPjFl92OdMz0g+3o8l88mTuaAvzg9JdOVDc0k3Oie//b/ZOvQO8c4E8A== +1438614153.950,1.000,0.001,HISTggAAAMp42u2WXQoCMQyE2/RnVwXxAN5lzyZ4Ay/qEXzpIgx8tBV8WMi8DF3yMylJtvfn6xHCLTSkxrGxbe/gcDgcDofD4fgiHkyn6jU5p8G6otgbvR/BPnf8S+NF7IvwKn47KsTVeBR397/Id42zSJ4MdVbhE9RJ9a1Q71nO10H9qqdIvAr59H6S8Cp2Ge5n6fRL6fRLgv4yyEf9pnYROHXsaH6oHwLop/ksoJvmNMN8k16b3B/24160wb1Ee2Z2H8+ej7rf//2/ih8yrQTu +1438614154.950,1.000,0.001,HISTggAAAMl42u2WzQ3CMAyF8xo3IT0xAEOwAbNVYgMWZQQOpJcnPTmthOjB38Vy7PivVtTb87WmdL2nL7lLdDk93ikIgiAIgiA4L9NOfwzqUP+H5K/smXQIPTn2Lc5M9k23Li9dVpKF/KqIu3TZKE8l+0xxG/mxnfM3oUPUU+i8Ds5jEXVy39npj+MWqgN0zt/PnPmbo1fR5yT6KGIPIe7znnr7rbDBvVb5uD6IuZhTD8c1px+Ie0ffFW9eOPjO4c/vZtrZF35c/9nABwJuBRs= +1438614155.950,1.000,0.001,HISTggAAAMB42u1WSQ7CMBCLJ5O2gIR4AH/p25D4QT/aJ3AgvViyphw4UMYXK55ktiRt7s/lUcptKm/Uzuhs81oSiUQikUj8J5AtOOS+YacOoQ/BPMVNvTeF7qQ3ig+hKz8jzW9k38Yn4s1+7nzpzO/nKzH7n8jvQP44v1HYnXS13kV9LvpQg7wrceTHqc5G++3inJiY5+Jc8H5XwSbOfRVxEdiVruIY9QHBOgR5YOe9tQ/vaRH5qDG+/N3CQf9Hv1IPXs36BOs= +1438614156.950,1.000,0.001,HISTggAAANd42u1WSQ7CMAy04yxNxYEH8Be+BhI/4KM8gQPpZaTBFWURkudixfEynqpWDpfrWWR/kgdsWB02HW8SCAQCgUAg8AwaEvxE50/pnqC+gl/IWeE96cXVYTPEGfEv8Q3OWKeAfxp2JveL3UH/CnkV+Deo3yE+kT6F6IB1itPf4D5DXAeeHfIKmQN1mmCeRnQ38h0M8meog3wyzFkgP5G+uvKcnXq68l6cfkbm9/qIw9Oc/z85+8LI/y2Of+teUoefEF3F4adv2qevxm3dw/pnfL/1ftA7XgwFiA== +1438614157.950,1.000,0.001,HISTggAAANt42u1W2w3CMAyMnSYpRZUYgF2YDYkNWIxRGIEPmp+TTidaXh++Hyt2fbaT2Onxcj2ndLilJ/IibZF+uqdAIBAIBAKBwHrYj+O6sNvKOpzwGPmvRJ4K64HwZpDd3oS9rwvwj2CvkB/q+3oCngniTZBXj7uDuN1/D/YZ/Bp810T+I8m7krxwfxvZ15nwN6i/gL2Qe2jkXDLxdyIbOTcn9yAL/UDuYRZxGE8h/VJFn6o+UnoX/A55GqnHxLxQ/e9ivtjG+WhCpjfzfmo+25f87UX9v71nm/f7AdoiBcw= +1438614158.950,1.000,0.001,HISTggAAANB42u1Xyw7CIBBkH4BNjPED/Be/rYkH7/6on+BBeplkAm2qvexcJgvs7HQDhN4erzml6zN9YY2lsd7fKRAIBAKBQCBwPGRnPSX6stIP47yynsC6DO/ThSvoZJI3NXbIMxgvEJ8aX0CvAFfIQ39L3hl0C6lfif8M8xNZj34U6ir5ToO4QiydPjjpc+34ctDNZJ8YjCvpt5N9kMi8kdiJP/Qt5P+p59fIfneirxvPP8uTwfHciUfvDeuc/9F49B769X3677ytdfRgH3v1Rz53MQV9 +1438614159.950,1.000,0.001,HISTggAAAMp42u2WSw7CMAxEE+fPhgtwF86GxA24JEuOwILC4kmjRGWF8GysTP0ZJ3Xa0/V2CeF4Dy+kzcbN2vkRHA6Hw+Fw/Aeib8FP7F8U9eJOXZ//vsV4+iWhS+ks4LPQkWET7MDzNxrqVMR18BX5DuC70DOQNyO+IQ/Xyt/Asw71VvRbkL+JfpPgmafhfIroK2HdxflyH21iE9ZNvA9R8IxnH8qP/lXoGot6bJFX82ZiPmxx3vNkPm1yHyi9YaIjLvb37f3l38+dup9JOgXi +1438614160.950,1.000,0.001,HISTggAAANV42u1WSwpCMQzs9KtPBHHtXTyb4A28kEfyCC58dTEwpO8hiJjZhKTtJE3TtKfr7RLC4R5eSLPELOP5ERwOh8PhcPwH4Cn4qfxh4bz3/86YHw1+0L9RIQk9C3/dXsne5U6MF1rf9UZ+G60vQt+Q7PatwT9RXJV4+vqj4JuIt8s96UXsp1I8UeS9CXsW+UgirkzjVfit4pxHJYRUdQVRDzDqnfcPo56VP4h7ouKP4n4mcV+ToSt+qy8Ewy8G+8baPobBc1rbH7/Vn/EhHn//FsbzBIf3Bag= +1438614161.950,1.000,0.000,HISTggAAAMF42u2Uyw3CMBBE95N1+ElpgF7SEi0g0QGNUgIHnMtIo8UCJA77LiN7NpON7fh8u19Flou88K7a1daHFEVRFEVRFP+HJuNR/M28AH8C3yDPiGoyP4FibiS+J3moc9dD8p591x0o1m3zjYy3+iP4M+k/QBcYn0heg/kAn31nAzXwI9kHXNdG1t1JPxPJd3I+2b5bUm+kf3aenOQoUfZfYH9BnvfB/x/7l8F7wpK6zNcP7zP90v2m8ltG+8vq9QnYswVI +1438614162.950,1.000,0.001,HISTggAAANB42u1WSQ7CMAz0RlrKhQfwF75WJH7AR3kCB5LLSKOkAlU9eC5W7Bk7iey0t+frIXJd5QuvVqu1+1sSiUQikUgkEseBdvza4SFsMJ8D34jeyH8ly8fqtTxR7QniE/gbr4B+Al2QvAX4uDbwNztDPlw3/QLnvhD/ArqAeAEe1i2D55sJD23jn0kc793Bb8B3si8nFvMoWbM+jU6/sX4Nst/o9LeTcwnot86XkTlg86yduTLCZ+/C1ndFBvk2qNM/1VPZF3vX+/X+DvOd+wCb1wVi +1438614163.950,1.000,0.001,HISTggAAANN42u1Vuw1CMQy0nR95EhIDsAsrsBISG7AoI1CQNCed/EApXuFrLFuXs+PEyfX5eohc7vJFGlaHtdtbAoFAIBAIBALHgTpxg7g563Hd9DPhG+Gjn4iP62e8kbyJxCf/NGwBfiE6FfSm3UjdmKcDr5M6mH+GOjrkaaC/gU6B/hWiV0l92E/0K+g20MnkvArpO96/Ss7DyL4yiRvRF3JfldQrpB/JmQcjNjv7kZ3zwnjqzDfbL+tLduZYf3xf0p/v1ur3cC9fZQ1W6RztX1mm/wHg7wU+ +1438614164.950,1.000,0.001,HISTggAAAM542u2WOw4CMQxE4yRe2IWCkmLvwtmQuAEX5QgU7DZPGiV8JEDyNFbseGzn42S+XM8pHY7pgbJIW2Q+3VIgEAgEAoHAL8Ginrf885O8in/9N1bwkr+K+UX4GewbjMnj8Od/1jGf/HvEGSBX/RbSYXfwjdAP0DPeJPgYv8BOfuY3on7KHeJkkR/3aUA+E3jpb4JH7WttnC91frKIVzv9lD/XwRvrkhrzTIwd4yruoXXeI/Kkzjx687XOfvFqn8ui73y7X3+6zn95H+0OQbIFFQ== +1438614165.950,1.000,0.000,HISTggAAAMJ42u1X2w3DIAz0AxJaVZ2gu3S2St2gi3aEfAR+TjqZKFL74/s5Bc7YGBuRx/vzErl/ZYd31s72HDOJRCKRSCQS56CZgr/kUfF9N6m3wC/qZuMyeHey9QZX0CNXols6F+IX7QqxX4EXYt9AN9a7db6AHtdr4H+F8UbGC9kvfg/9lcSJjPuK8tvI+aBOyfka+R9Rkjcl51uDOqqkbj3ol0L0TvwJzJegzhk76R8N+vJoHx+9B+xkXDIZn//o/tQNdQQFvw== +1438614166.950,1.000,0.001,HISTggAAAMt42u1W2w3CMAy0Yzdt4QMGYBdWA4kNWJQR+CDl46SToxaBkHw/p/hxdhon6ul2v4ocL/KCNdbG5fyQRCKRSCQSicT/QVeu3/+BJK4E8QbrhZ3Yjeh7kIf+MdAfwD4EXEHXwT8DG/AI/kXvAHoz0a+kr6nxnsTvSB81qDMF+3bQr6QOngPWNXI+DnNgZL6c+I3YMV/Id2DzK2SuWD9C8ti90M77aUF8IfmlMy5iCd4B6cyTTr8Efa99B7+Vv7Xup+roxvhf7VefY2cFUg== +1438614167.950,1.000,0.001,HISTggAAAM542u1WOw7CMAy1YzeltAMH4C49GxIDOxflCAwky5OenDIgBr/lyZ/Edmu7vd6fN5HLQz6wxtq47C9JJBKJRCKRSHDon8cr5JwSfSF29MP/RgviOPE3EtdB33kC2QL7BH5L47lxJfIJ5AXuwXPdfga/XsfWeIX8nNS7Qv4VZHxfmC/qN5J/Jc/TSZ1K4jGeIW+s14ie2Vk/SNAXrJ+NnBvtTw/mh+WvwXyzOYjmPNoXbB6j/aAH7y+D9+ngftOgXvtyb/56fycOfu/eMBwFYA== +1438614168.950,1.000,0.000,HISTggAAAM142u1WSQ7CMAz0uEnT0gMf4C+8DYkDdz7KEziQXEYapSpUKpLnYjkd25PFSS/3583s/LAPhmpRrV9fFggEAoFAILAHcLA8/zr/Bt/Ig9Cz1neyg/ivbH4in+Mmwc/VjqKOE5/rtPhC4zPVbd8XqgeR34nHOk+Uf6Fx1lEoPhE/CV2cZyb+RPoyjfP8i+CxLhP6c0dnEucEYn/VuvuPzuEozlMmPvdB6eiA8JU+E/NAp5+xsr/VOvnGvNa5F769D3Gwe3ivd0vu7xtTFAWE +1438614169.950,1.000,0.001,HISTggAAAM942u1WOw5CIRDcDx+fmngB7+LZTLyBF7W0tBCaSSYQtHjFTjNhmf0GCNfH8y5yecsX3lgb2+0lgUAgEAgEAoH9QoFX90cw4udE3+0JWIm9gB/6G/mvVhKvQtxu3xofQedEn4G7/gz2rj+BfQNOxK+QvjPsF5gH1n+AdYW6CuiwPyfzMvBLsC5kjjboB+Pa5PlNkC+T8+EQV4kuEZ2Qc55JnUb6xjxC9Gx+s/ffSN1sjjp573XQ7yiuTvrJ4nski++b/ljHv97vveTTD11pBdo= +1438614170.950,1.000,0.001,HISTggAAAMx42u1V2w3CMAz0I24LBXWB7sJsSGzAIozGCHyQ/Jx0CqhI5cP3Y7V27mzHddfb/SqyPOQNr1artctTEolEIpFIJD6BZl2bYDvXp533pRPvJN5IfUF4DPiM6BSwQnQb/wD+9jxCPgPYgLhC/KdqjxDf4mY4j/4J7ELyDuiDE/8Z+EbSH9R3ojORfs3kvprOAeKD8OD56PBinkbuXcm8YT5svpT0W4mukblk+bPvSDr8Qup1ot/bM/rl3tON+0t/tLf+7f+ge+m+AE1MBZc= +1438614171.950,1.000,0.001,HISTggAAANB42u1WSQ7CMAy0HTcByoEH8BfehsQP+ChP4JJyGGnkBBVOnsuoXiaTpWmvj+dd5CIdpbN2tttLEolEIpFIJBL/gwILed5gpM6CfiU6OujLSF+UL8AOeWf/pZ0bxA+dF2ADHYxX0HMyzqa/dj5DvxMfjfg6QT/qOzD6MNCrED+S/EryyCVYj4WsW4W8k3OB+29Qz/avkLpCWMg5jOLot02ebwc9D9ZDg/E1eD918D7QYP6j7/2391JUL5M+ZnVl0r/uPO5e34Vf637m+wbY+ATY +1438614172.950,1.000,0.001,HISTggAAAMt42u2W3Q3CMAyE7fw0UCFYgF06GxIbsAijMQIPtC8nfXLFE0K+l1Mc+3pxKivX++NmdnnaB3VlX7ksL0skEolEIpFI/C78y7rt3VdExyVusNZ4C3R13SW/CbvsT7I/QbzD/gz1BXx34aPUH4Q3v0Pqinxf9RvUDeETnM/B/1n0Z/BL/ad+NfDfoZ8d1gN8aJ7D/9VA34Er+C6Bb4M81Y3yPKjvcI8V/HowB6gPU9Af3zlf9F4on+aI79S34FzU92he1j+b5/4GQCEFnA== +1438614173.950,1.000,0.001,HISTggAAAM942u1UQQoCMQxsmma7rYgf8C/7NsEf+FGPHj3YvQwMWXZFEDKXIW2TTNo01/vjltLllT7QwTI4L88UCAQCgUDgPyBRT7z7AX9x4mayn4mNrGS9wH4BxvN9sEFeBZ7AnoEN7DV+A78z2A38V66gG+NW0IV8IudmiNuh7gZ+RnRg3Ap1daKrEBvz4rtk8DPSD0r6SEifTOScknVz+o7p8vq5OHrF6XOWz/tv5vzDRO5XHNYvzQ3277fOq+zYe3XtnYu6UYcczPPreU/3367WBfc= +1438614174.950,1.000,0.001,HISTggAAANF42u1WSQ7CMAx0FjsBLjyAv/C2SvyAj/IEDqQcRho5RYAE8lxG9TZO0rg9Xa6LyPEmD5TBaXA+r55AIBAIBAKBwC/i+V9H7OgvEJcndbJTb7Ur2JHb4AqM+Qr+TvQxbzfYiK5B3B7YiB3rHCC+QZ9K8nF9LE+d/TMSj3Ub5HfYVyP+Rs65k76UnA/qVMgz8oz2TOqw95O9p9XpsxJdplede5Cd+4n9lMn1JMLYh5B7n5z5IS/Oha3zaOucm+3Hq5M+pP/uuf5t3b/9Tt4BQwYF/A== +1438614175.950,1.000,0.004,HISTggAAAPh42u3Vy03EMBAAUH8Sdlkh0QC90Ma2g0QHNEoHcFhyGWk0C+SwoPculp3xJ44zfnp9e2ntfG4X86vsl+Lx4/m9AQCQ6zc6fv9m3FaO8HwUcaPo15K4fuU6TqG+hPohqbcQf5e0b/FriDuE+hLWM0O/rf4Q+sdxeui3hrglWc99aD8l7UuYZ3t+TOZdk/nifh/DeHGfZtE+ku+b7ctM9jk7B0tynmZSjmS/Y/ty5fmfxTkfyXv2Im4U/201T2YW+aL6T0fxHu2H+afKX2On/Nd3yrO/zct95/Fu/T77b/czAAAAAAAAAAAAAAAAAAAAAAD8Bf0TEUcGgg== +1438614176.950,1.000,0.001,HISTggAAANZ42u1WyQ0CMQy0nWvhRQH0Qm1IdEBTlEMJPEg+g0bOCnGs5PlYTpzxWBs7e7xczyKHmzyRutVu7XSXQCAQCAQCgX+CbiyfvsmjH9KpxDcnzkhdbB33hy3OubGfu23wv2rg77qtYPF8JfyFxGXIM+IW8PdgG8QZ+AvwMr3Ig/UZ0dmI7kL4ColHPRnqZPmN+Nm5H4nEZScP48H16vQl5jfyfczpn+T0lZD7rpPnjOwzn+lOTt/qJL9O6pudk7pybsnknNKVc/Pb782v3y3duP4XngesZQWe +1438614177.950,1.000,0.001,HISTggAAAM142u1V2w3CMAz0I4+2XwzALqzECkhswKKMwAfNz0knV1CJSvh+LCfns9Payfn+uImcrvKGr1ZXa5enJBKJRCKRSPwz9GD12E71GtFTYiN9J3FDvwGvgO9Eh/Emst/Bb8CvsD7sDLyhs5B1J3kL5JkhTwV+J3UshFeA30G3kv2J+Bacq5H8heQrQTz2USN9IkFfOOmnSvov6jeBeBbHdHGelPj4Xdh5ajBPSubXAvvpfecB3zbyZCP/1/e47vwe6M71H+1d+vr/vADt2wV1 +1438614178.950,0.125,0.000,HISTggAAAGt42pNpmdzIwMDnwAABzFCaEUoz2X9gGAWjYBSMglEwCkbBKBjMgHHUfzQBTDT2DyMBcUYS/Y/LPCYC/mOkkz+J9R+hcGEm0RxC4UisOUwkmsdAJXXkxjsjlexjpFP+ZqSR/oEqPxgBzSoDcw== +1438614179.075,0.004,0.000,HISTggAAAB142pNpmdzIwMDAwQABzFCaEUoz2X9gQAEAYqkC8g== diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ShortHistogramEncodingTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ShortHistogramEncodingTests.cs new file mode 100644 index 0000000000..56c0802b50 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ShortHistogramEncodingTests.cs @@ -0,0 +1,29 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal sealed class ShortHistogramEncodingTests : HistogramEncodingTestBase + { + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantDigits) + { + //return new ShortHistogram(highestTrackableValue, numberOfSignificantDigits); + return HistogramFactory.With16BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantDigits) + .Create(); + } + + internal override void LoadFullRange(IRecorder source) + { + for (long i = 0L; i < DefaultHighestTrackableValue; i += 1000L) + { + source.RecordValue(i); + } + source.RecordValue(DefaultHighestTrackableValue); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ShortHistogramTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ShortHistogramTests.cs new file mode 100644 index 0000000000..f55ba346ee --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/ShortHistogramTests.cs @@ -0,0 +1,32 @@ +// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. +// + +using Xunit; + +namespace HdrHistogram.UnitTests +{ + + internal class ShortHistogramTests : HistogramTestBase + { + protected override int WordSize => sizeof(short); + + internal override HistogramBase Create(long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new ShortHistogram(highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With16BitBucketSize() + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .Create(); + } + + internal override HistogramBase Create(long lowestTrackableValue, long highestTrackableValue, int numberOfSignificantValueDigits) + { + //return new ShortHistogram(lowestTrackableValue, highestTrackableValue, numberOfSignificantValueDigits); + return HistogramFactory.With16BitBucketSize() + .WithValuesFrom(lowestTrackableValue) + .WithValuesUpTo(highestTrackableValue) + .WithPrecisionOf(numberOfSignificantValueDigits) + .Create(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Spin.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Spin.cs new file mode 100644 index 0000000000..16a2cbdb47 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/Spin.cs @@ -0,0 +1,25 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using Xunit; + +//It seems for the most reliable tests, running them single threaded doesn't +// saturate the OS Thread scheduler and therfore doesn't lead to extended +// pauses when executing one of our many dealy/sleep paths. +//I have tried to consolidate them all to use this one class, so if they are +// abandoned, then this DisableTestParallelization can be removed too. -LC +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace HdrHistogram.UnitTests +{ + public static class Spin + { + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static void Wait(TimeSpan period) + { + var sw = Stopwatch.StartNew(); + SpinWait.SpinUntil(() => period < sw.Elapsed); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/TimeStampTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/TimeStampTests.cs new file mode 100644 index 0000000000..21d12f5f05 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/OSS/HdrHistogram/TimeStampTests.cs @@ -0,0 +1,28 @@ +using System; +using System.Diagnostics; +using FluentAssertions; +using Xunit; + +namespace HdrHistogram.UnitTests +{ + public class TimeStampTests + { + [Fact] + public void TimeStamp_values_are_accurate() + { + var delay = TimeSpan.FromSeconds(1); + var expected = TimeStamp.Seconds(delay.Seconds); + long minAccepted = (long)(expected * 0.95); + long maxAccepted = (long)(expected * 1.05); + + var start = Stopwatch.GetTimestamp(); + Spin.Wait(delay); + var end = Stopwatch.GetTimestamp(); + var actual = end - start; + + actual.Should().BeInRange(minAccepted, maxAccepted); + Assert.Equal(TimeStamp.Seconds(60), TimeStamp.Minutes(1)); + Assert.Equal(TimeStamp.Minutes(60), TimeStamp.Hours(1)); + } + } +} \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Pagination/FlakyDocumentContainer.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Pagination/FlakyDocumentContainer.cs index fcdc940dd7..6d14293d97 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Pagination/FlakyDocumentContainer.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Pagination/FlakyDocumentContainer.cs @@ -202,6 +202,16 @@ public Task> MonadicChangeFeedAsync( state: feedRangeState.State))); } + if (this.ShouldThrowException(out Exception exception)) + { + throw exception; + } + + if (this.ShouldReturnFailure(out Exception failure)) + { + return Task.FromResult(TryCatch.FromException(failure)); + } + return this.documentContainer.MonadicChangeFeedAsync( feedRangeState, changeFeedPaginationOptions, @@ -253,17 +263,39 @@ private bool ShouldReturnEmptyPage() => (this.failureConfigs != null) && this.failureConfigs.InjectEmptyPages && ((this.random.Next() % 2) == 0); + private bool ShouldThrowException(out Exception exception) + { + exception = this.failureConfigs.ThrowException; + return this.failureConfigs != null && this.failureConfigs.ThrowException != null; + } + + private bool ShouldReturnFailure(out Exception exception) + { + exception = this.failureConfigs.ReturnFailure; + return this.failureConfigs != null && this.failureConfigs.ReturnFailure != null; + } + public sealed class FailureConfigs { - public FailureConfigs(bool inject429s, bool injectEmptyPages) + public FailureConfigs( + bool inject429s, + bool injectEmptyPages, + Exception throwException = null, + Exception returnFailure = null) { this.Inject429s = inject429s; this.InjectEmptyPages = injectEmptyPages; + this.ThrowException = throwException; + this.ReturnFailure = returnFailure; } public bool Inject429s { get; } public bool InjectEmptyPages { get; } + + public Exception ThrowException { get; } + + public Exception ReturnFailure { get; } } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs index 7d3b400a81..a0c2f408e8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/GlobalPartitionEndpointManagerTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Linq; using System.Net; using System.Net.Http; + using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Documents; @@ -66,20 +67,123 @@ public async Task TestWriteForbiddenScenarioAsync() TransportClientHandlerFactory = (original) => mockTransport.Object, }; - CosmosClient customClient = new CosmosClient( + using (CosmosClient customClient = new CosmosClient( globalEndpoint, Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), - cosmosClientOptions); + cosmosClientOptions)) + { + + + + Container container = customClient.GetContainer(databaseName, containerName); + + ToDoActivity toDoActivity = new ToDoActivity() + { + Id = "TestItem", + Pk = "TestPk" + }; + + ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + mockTransport.VerifyAll(); + mockHttpHandler.VerifyAll(); + + // Clears all the setups. No network calls should be done on the next operation. + mockHttpHandler.Reset(); + mockTransport.Reset(); + mockTransport.Setup(x => x.Dispose()); + + MockSetupsHelper.SetupCreateItemResponse( + mockTransport, + secondaryRegionPrimaryReplicaUri); + + ToDoActivity toDoActivity2 = new ToDoActivity() + { + Id = "TestItem2", + Pk = "TestPk" + }; + + response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.Pk)); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + } + + await Task.Delay(TimeSpan.FromMinutes(5)); + Console.WriteLine("done"); + } + + [TestMethod] + public async Task TestServiceUnavailableExceptionScenarioAsync() + { + GlobalPartitionEndpointManagerTests.SetupAccountAndCacheOperations( + out string secondaryRegionNameForUri, + out string globalEndpoint, + out string secondaryRegionEndpiont, + out string databaseName, + out string containerName, + out ResourceId containerResourceId, + out Mock mockHttpHandler, + out IReadOnlyList primaryRegionPartitionKeyRangeIds, + out TransportAddressUri primaryRegionprimaryReplicaUri); + + Mock mockTransport = new Mock(MockBehavior.Strict); + + MockSetupsHelper.SetupServiceUnavailableException( + mockTransport, + primaryRegionprimaryReplicaUri); + + // Partition key ranges are the same in both regions so the SDK + // does not need to go the secondary to get the partition key ranges. + // Only the addresses need to be mocked on the secondary + MockSetupsHelper.SetupAddresses( + mockHttpHandler: mockHttpHandler, + partitionKeyRangeId: primaryRegionPartitionKeyRangeIds.First(), + regionEndpoint: secondaryRegionEndpiont, + regionName: secondaryRegionNameForUri, + containerResourceId: containerResourceId, + primaryReplicaUri: out TransportAddressUri secondaryRegionPrimaryReplicaUri); + + MockSetupsHelper.SetupCreateItemResponse( + mockTransport, + secondaryRegionPrimaryReplicaUri); + + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() + { + EnablePartitionLevelFailover = true, + ConsistencyLevel = Cosmos.ConsistencyLevel.Strong, + ApplicationPreferredRegions = new List() + { + Regions.EastUS, + Regions.WestUS + }, + HttpClientFactory = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)), + TransportClientHandlerFactory = (original) => mockTransport.Object, + }; + + using CosmosClient customClient = new CosmosClient( + globalEndpoint, + Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), + cosmosClientOptions); Container container = customClient.GetContainer(databaseName, containerName); ToDoActivity toDoActivity = new ToDoActivity() { - id = "TestItem", - pk = "TestPk" + Id = "TestItem", + Pk = "TestPk" }; - ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.pk)); + // First create will fail because it is not certain if the payload was sent or not. + try + { + await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); + Assert.Fail("Should throw an exception"); + } + catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.ServiceUnavailable) + { + Assert.IsNotNull(ce); + } + + ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); mockTransport.VerifyAll(); mockHttpHandler.VerifyAll(); @@ -87,6 +191,7 @@ public async Task TestWriteForbiddenScenarioAsync() // Clears all the setups. No network calls should be done on the next operation. mockHttpHandler.Reset(); mockTransport.Reset(); + mockTransport.Setup(x => x.Dispose()); MockSetupsHelper.SetupCreateItemResponse( mockTransport, @@ -94,16 +199,16 @@ public async Task TestWriteForbiddenScenarioAsync() ToDoActivity toDoActivity2 = new ToDoActivity() { - id = "TestItem2", - pk = "TestPk" + Id = "TestItem2", + Pk = "TestPk" }; - response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.pk)); + response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.Pk)); Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); } [TestMethod] - public async Task TestServiceUnavailableExceptionScenarioAsync() + public async Task TestRequestTimeoutExceptionScenarioAsync() { GlobalPartitionEndpointManagerTests.SetupAccountAndCacheOperations( out string secondaryRegionNameForUri, @@ -118,7 +223,7 @@ public async Task TestServiceUnavailableExceptionScenarioAsync() Mock mockTransport = new Mock(MockBehavior.Strict); - MockSetupsHelper.SetupServiceUnavailableException( + MockSetupsHelper.SetupRequestTimeoutException( mockTransport, primaryRegionprimaryReplicaUri); @@ -150,7 +255,7 @@ public async Task TestServiceUnavailableExceptionScenarioAsync() TransportClientHandlerFactory = (original) => mockTransport.Object, }; - CosmosClient customClient = new CosmosClient( + using CosmosClient customClient = new CosmosClient( globalEndpoint, Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), cosmosClientOptions); @@ -159,22 +264,22 @@ public async Task TestServiceUnavailableExceptionScenarioAsync() ToDoActivity toDoActivity = new ToDoActivity() { - id = "TestItem", - pk = "TestPk" + Id = "TestItem", + Pk = "TestPk" }; // First create will fail because it is not certain if the payload was sent or not. try { - await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.pk)); + await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); Assert.Fail("Should throw an exception"); } - catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.ServiceUnavailable) + catch (CosmosException ce) when (ce.StatusCode == HttpStatusCode.RequestTimeout) { Assert.IsNotNull(ce); } - ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.pk)); + ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); mockTransport.VerifyAll(); mockHttpHandler.VerifyAll(); @@ -182,6 +287,7 @@ public async Task TestServiceUnavailableExceptionScenarioAsync() // Clears all the setups. No network calls should be done on the next operation. mockHttpHandler.Reset(); mockTransport.Reset(); + mockTransport.Setup(x => x.Dispose()); MockSetupsHelper.SetupCreateItemResponse( mockTransport, @@ -189,11 +295,11 @@ public async Task TestServiceUnavailableExceptionScenarioAsync() ToDoActivity toDoActivity2 = new ToDoActivity() { - id = "TestItem2", - pk = "TestPk" + Id = "TestItem2", + Pk = "TestPk" }; - response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.pk)); + response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.Pk)); Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); } @@ -206,9 +312,9 @@ private static void SetupAccountAndCacheOperations( out ResourceId containerResourceId, out Mock mockHttpHandler, out IReadOnlyList primaryRegionPartitionKeyRangeIds, - out TransportAddressUri primaryRegionprimaryReplicaUri) + out TransportAddressUri primaryRegionprimaryReplicaUri, + [CallerMemberName] string accountName = nameof(GlobalPartitionEndpointManagerTests)) { - string accountName = "testAccount"; string primaryRegionNameForUri = "eastus"; secondaryRegionNameForUri = "westus"; globalEndpoint = $"https://{accountName}.documents.azure.com:443/"; @@ -274,22 +380,5 @@ private static void SetupAccountAndCacheOperations( containerResourceId: containerResourceId, primaryReplicaUri: out primaryRegionprimaryReplicaUri); } - - private class ToDoActivity - { - public string id { get; set; } - public string pk { get; set; } - - public override bool Equals(object obj) - { - if (!(obj is ToDoActivity input)) - { - return false; - } - - return string.Equals(this.id, input.id) - && string.Equals(this.pk, input.pk); - } - } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/MockSetupsHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/MockSetupsHelper.cs index bf78a63b00..57ef31767a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/MockSetupsHelper.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/MockSetupsHelper.cs @@ -30,6 +30,23 @@ public static void SetupStrongAccountProperties( string endpoint, IList writeRegions, IList readRegions) + { + HttpResponseMessage httpResponseMessage = MockSetupsHelper.CreateStrongAccount( + accountName, + writeRegions, + readRegions); + + Uri endpointUri = new Uri(endpoint); + mockHttpClientHandler.Setup(x => x.SendAsync( + It.Is(x => x.RequestUri == endpointUri), + It.IsAny())) + .Returns((request, cancellationToken) => Task.FromResult(httpResponseMessage)); + } + + public static HttpResponseMessage CreateStrongAccount( + string accountName, + IList writeRegions, + IList readRegions) { AccountProperties accountProperties = new AccountProperties() { @@ -59,23 +76,11 @@ public static void SetupStrongAccountProperties( } }; - Uri endpointUri = new Uri(endpoint); - mockHttpClientHandler.Setup(x => x.SendAsync( - It.Is(x => x.RequestUri == endpointUri), - It.IsAny())) - .Returns((request, cancellationToken) => - { - if (endpointUri == request.RequestUri) - { - Console.WriteLine("Itworks"); - } - - return Task.FromResult(new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(JsonConvert.SerializeObject(accountProperties)) - }); - }); + return new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(JsonConvert.SerializeObject(accountProperties)) + }; } public static void SetupContainerProperties( @@ -241,11 +246,7 @@ internal static void SetupWriteForbiddenException( TransportAddressUri physicalUri) { mockTransportClient.Setup(x => x.InvokeResourceOperationAsync(physicalUri, It.IsAny())) - .Returns(() => - { - Console.WriteLine($"WriteForbiddenThrown: {physicalUri}"); - throw new ForbiddenException($"Mock write forbidden exception on URI:{physicalUri}", SubStatusCodes.WriteForbidden); - }); + .Returns(() => throw new ForbiddenException($"Mock write forbidden exception on URI:{physicalUri}", SubStatusCodes.WriteForbidden)); } internal static void SetupServiceUnavailableException( @@ -253,11 +254,15 @@ internal static void SetupServiceUnavailableException( TransportAddressUri physicalUri) { mockTransportClient.Setup(x => x.InvokeResourceOperationAsync(physicalUri, It.IsAny())) - .Returns(() => - { - Console.WriteLine($"WriteForbiddenThrown: {physicalUri}"); - throw new ServiceUnavailableException($"Mock write forbidden exception on URI:{physicalUri}", physicalUri.Uri); - }); + .Returns(() => throw new ServiceUnavailableException($"Mock write forbidden exception on URI:{physicalUri}", physicalUri.Uri)); + } + + internal static void SetupRequestTimeoutException( + Mock mockTransportClient, + TransportAddressUri physicalUri) + { + mockTransportClient.Setup(x => x.InvokeResourceOperationAsync(physicalUri, It.IsAny())) + .Returns(() => throw new RequestTimeoutException($"Mock request timeout exception on URI:{physicalUri}", physicalUri.Uri)); } internal static void SetupCreateItemResponse( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/RegionFailoverTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/RegionFailoverTests.cs new file mode 100644 index 0000000000..70cf0950c0 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PartitionKeyRangeFailoverTests/RegionFailoverTests.cs @@ -0,0 +1,213 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tests +{ + 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 Microsoft.Azure.Documents; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; + + [TestClass] + public class RegionFailoverTests + { + [TestMethod] + public async Task TestHttpRequestExceptionScenarioAsync() + { + // testhost.dll.config sets it to 2 seconds which causes it to always expire before retrying. Remove the override. + System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"] = "500"; + + string accountName = nameof(TestHttpRequestExceptionScenarioAsync); + string primaryRegionNameForUri = "eastus"; + string secondaryRegionNameForUri = "westus"; + string globalEndpoint = $"https://{accountName}.documents.azure.com:443/"; + Uri globalEndpointUri = new Uri(globalEndpoint); + string primaryRegionEndpoint = $"https://{accountName}-{primaryRegionNameForUri}.documents.azure.com"; + string secondaryRegionEndpiont = $"https://{accountName}-{secondaryRegionNameForUri}.documents.azure.com"; + string databaseName = "testDb"; + string containerName = "testContainer"; + string containerRid = "ccZ1ANCszwk="; + ResourceId containerResourceId = ResourceId.Parse(containerRid); + + List writeRegion = new List() + { + new AccountRegion() + { + Name = "East US", + Endpoint = $"{primaryRegionEndpoint}:443/" + } + }; + + List readRegions = new List() + { + new AccountRegion() + { + Name = "East US", + Endpoint = $"{primaryRegionEndpoint}:443/" + }, + new AccountRegion() + { + Name = "West US", + Endpoint = $"{secondaryRegionEndpiont}:443/" + } + }; + + List writeRegionFailedOver = new List() + { + new AccountRegion() + { + Name = "West US", + Endpoint = $"{secondaryRegionEndpiont}:443/" + } + }; + + List readRegionsFailedOver = new List() + { + + new AccountRegion() + { + Name = "West US", + Endpoint = $"{secondaryRegionEndpiont}:443/" + }, + new AccountRegion() + { + Name = "East US", + Endpoint = $"{primaryRegionEndpoint}:443/" + }, + }; + + // Create a mock http handler to inject gateway responses. + // MockBehavior.Strict ensures that only the mocked APIs get called + Mock mockHttpHandler = new Mock(MockBehavior.Strict); + + + mockHttpHandler.Setup(x => x.SendAsync( + It.Is(m => m.RequestUri == globalEndpointUri || m.RequestUri.ToString().Contains(primaryRegionNameForUri)), + It.IsAny())).Throws(new HttpRequestException("Mock HttpRequestException to simulate region being down")); + + int count = 0; + mockHttpHandler.Setup(x => x.SendAsync( + It.Is(x => x.RequestUri == new Uri(secondaryRegionEndpiont)), + It.IsAny())) + .Returns((request, cancellationToken) => + { + // Simulate the legacy gateway being down. After 40 requests simulate the write region pointing to new location. + count++; + if (count < 2) + { + return Task.FromResult(MockSetupsHelper.CreateStrongAccount(accountName, writeRegion, readRegions)); + } + else + { + return Task.FromResult(MockSetupsHelper.CreateStrongAccount(accountName, writeRegionFailedOver, readRegionsFailedOver)); + } + }); + + + MockSetupsHelper.SetupContainerProperties( + mockHttpHandler: mockHttpHandler, + regionEndpoint: secondaryRegionEndpiont, + databaseName: databaseName, + containerName: containerName, + containerRid: containerRid); + + MockSetupsHelper.SetupPartitionKeyRanges( + mockHttpHandler: mockHttpHandler, + regionEndpoint: secondaryRegionEndpiont, + containerResourceId: containerResourceId, + partitionKeyRangeIds: out IReadOnlyList secondaryRegionPartitionKeyRangeIds); + + MockSetupsHelper.SetupAddresses( + mockHttpHandler: mockHttpHandler, + partitionKeyRangeId: secondaryRegionPartitionKeyRangeIds.First(), + regionEndpoint: secondaryRegionEndpiont, + regionName: secondaryRegionNameForUri, + containerResourceId: containerResourceId, + primaryReplicaUri: out TransportAddressUri secondaryRegionprimaryReplicaUri); + + Mock mockTransport = new Mock(MockBehavior.Strict); + + MockSetupsHelper.SetupRequestTimeoutException( + mockTransport, + secondaryRegionprimaryReplicaUri); + + // Partition key ranges are the same in both regions so the SDK + // does not need to go the secondary to get the partition key ranges. + // Only the addresses need to be mocked on the secondary + MockSetupsHelper.SetupAddresses( + mockHttpHandler: mockHttpHandler, + partitionKeyRangeId: secondaryRegionPartitionKeyRangeIds.First(), + regionEndpoint: secondaryRegionEndpiont, + regionName: secondaryRegionNameForUri, + containerResourceId: containerResourceId, + primaryReplicaUri: out TransportAddressUri secondaryRegionPrimaryReplicaUri); + + MockSetupsHelper.SetupCreateItemResponse( + mockTransport, + secondaryRegionPrimaryReplicaUri); + + CosmosClientOptions cosmosClientOptions = new CosmosClientOptions() + { + EnablePartitionLevelFailover = true, + ConsistencyLevel = Cosmos.ConsistencyLevel.Strong, + ApplicationPreferredRegions = new List() + { + Regions.EastUS, + Regions.WestUS + }, + HttpClientFactory = () => new HttpClient(new HttpHandlerHelper(mockHttpHandler.Object)), + TransportClientHandlerFactory = (original) => mockTransport.Object, + }; + + using (CosmosClient customClient = new CosmosClient( + globalEndpoint, + Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())), + cosmosClientOptions)) + { + Container container = customClient.GetContainer(databaseName, containerName); + + ToDoActivity toDoActivity = new ToDoActivity() + { + Id = "TestItem", + Pk = "TestPk" + }; + + ItemResponse response = await container.CreateItemAsync(toDoActivity, new Cosmos.PartitionKey(toDoActivity.Pk)); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + mockTransport.VerifyAll(); + mockHttpHandler.VerifyAll(); + + // Clears all the setups. No network calls should be done on the next operation. + mockHttpHandler.Reset(); + mockTransport.Reset(); + + MockSetupsHelper.SetupCreateItemResponse( + mockTransport, + secondaryRegionPrimaryReplicaUri); + + ToDoActivity toDoActivity2 = new ToDoActivity() + { + Id = "TestItem2", + Pk = "TestPk" + }; + + response = await container.CreateItemAsync(toDoActivity2, new Cosmos.PartitionKey(toDoActivity2.Pk)); + Assert.AreEqual(HttpStatusCode.Created, response.StatusCode); + mockTransport.Setup(x => x.Dispose()); + + // Reset it back to the override to avoid impacting other tests. + System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"] = "2"; + } + + await Task.Delay(TimeSpan.FromMinutes(2)); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PatchOperationTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PatchOperationTests.cs index 0736ad490a..913c8c86e7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PatchOperationTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/PatchOperationTests.cs @@ -72,14 +72,26 @@ private static void ValidateOperations(PatchOperation patchOperation, PatchOp { string expected; CosmosSerializer cosmosSerializer = new CosmosJsonDotNetSerializer(); - Stream stream = cosmosSerializer.ToStream(value); - using (StreamReader streamReader = new StreamReader(stream)) + using (Stream stream = cosmosSerializer.ToStream(value)) { - expected = streamReader.ReadToEnd(); + using (StreamReader streamReader = new StreamReader(stream)) + { + expected = streamReader.ReadToEnd(); + } } - Assert.IsTrue(patchOperation.TrySerializeValueParameter(new CustomSerializer(), out string valueParam)); - Assert.AreEqual(expected, valueParam); + Assert.IsTrue(patchOperation.TrySerializeValueParameter(new CustomSerializer(), out Stream valueParam)); + + string actual; + using (valueParam) + { + using (StreamReader streamReader = new StreamReader(valueParam)) + { + actual = streamReader.ReadToEnd(); + } + } + + Assert.AreEqual(expected, actual); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs index b63c427165..9eaabcd7b0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Parser/ScalarExpressionSqlParserBaselineTests.cs @@ -130,6 +130,32 @@ public void Binary() description: "Logical OR > String Concat", scalarExpression: "1 || 2 OR 3 || 4")); + // Binary with logical expressions + inputs.Add(CreateInput( + description: "AND with LIKE expressions", + scalarExpression: "r.name LIKE 12 AND r.name LIKE 34")); + inputs.Add(CreateInput( + description: "OR with LIKE expression", + scalarExpression: "r.name LIKE 12 OR r.name LIKE 34")); + inputs.Add(CreateInput( + description: "AND with IN expression", + scalarExpression: "r.name IN (1,2,3) AND r.name IN (4,5,6)")); + inputs.Add(CreateInput( + description: "OR with IN expression", + scalarExpression: "r.name IN (1,2,3) OR r.name IN (4,5,6)")); + inputs.Add(CreateInput( + description: "Double AND", + scalarExpression: "r.age = 1 AND r.age = 2 AND r.age = 3")); + inputs.Add(CreateInput( + description: "Double OR", + scalarExpression: "r.age = 1 OR r.age = 2 OR r.age = 3")); + inputs.Add(CreateInput( + description: "AND and then OR", + scalarExpression: "r.age = 1 AND r.age = 2 OR r.age = 3")); + inputs.Add(CreateInput( + description: "OR and then AND", + scalarExpression: "r.age = 1 OR r.age = 2 AND r.age = 3")); + // Negative inputs.Add(CreateInput(description: "Missing Right", scalarExpression: "42 +")); inputs.Add(CreateInput(description: "Missing Left", scalarExpression: "AND 1337")); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Pipeline/OrderByCrossPartitionQueryPipelineStageTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Pipeline/OrderByCrossPartitionQueryPipelineStageTests.cs index 41cb2220d1..522d23496a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Pipeline/OrderByCrossPartitionQueryPipelineStageTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/Pipeline/OrderByCrossPartitionQueryPipelineStageTests.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Tests.Query.Pipeline using System.Linq; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.CosmosElements; + using Microsoft.Azure.Cosmos.CosmosElements.Numbers; using Microsoft.Azure.Cosmos.Pagination; using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.Exceptions; @@ -27,6 +28,25 @@ namespace Microsoft.Azure.Cosmos.Tests.Query.Pipeline [TestClass] public class OrderByCrossPartitionQueryPipelineStageTests { + private static IReadOnlyList<(string serializedToken, CosmosElement element)> TokenTestData() + { + Guid guid = Guid.Parse("69D5AB17-C94A-4173-A278-B59D0D9C7C37"); + byte[] randomBytes = guid.ToByteArray(); + string hexString = PartitionKeyInternal.HexConvert.ToHex(randomBytes, 0, randomBytes.Length); + + return new List<(string, CosmosElement)> + { + ("[42, 37]", CosmosArray.Parse("[42, 37]")), + ($@"{{C_Binary(""0x{hexString}"")}}", CosmosBinary.Create(new ReadOnlyMemory(randomBytes))), + ("false", CosmosBoolean.Create(false)), + ($@"{{C_Guid(""{guid}"")}}", CosmosGuid.Create(guid)), + ("null", CosmosNull.Create()), + ("1", CosmosInt64.Create(1)), + ("{\"foo\": false}", CosmosObject.Parse("{\"foo\": false}")), + ("asdf", CosmosString.Create("asdf")) + }; + } + [TestMethod] public void MonadicCreate_NullContinuationToken() { @@ -118,88 +138,144 @@ public void MonadicCreate_NonParallelContinuationToken() public void MonadicCreate_SingleOrderByContinuationToken() { Mock mockDocumentContainer = new Mock(); + IReadOnlyList<(string serializedToken, CosmosElement element)> tokens = TokenTestData(); - ParallelContinuationToken parallelContinuationToken = new ParallelContinuationToken( - token: "asdf", - range: new Documents.Routing.Range("A", "B", true, false)); - - OrderByContinuationToken orderByContinuationToken = new OrderByContinuationToken( - parallelContinuationToken, - new List() { new OrderByItem(CosmosObject.Create(new Dictionary() { { "item", CosmosString.Create("asdf") } })) }, - rid: "rid", - skipCount: 42, - filter: "filter"); - - TryCatch monadicCreate = OrderByCrossPartitionQueryPipelineStage.MonadicCreate( - documentContainer: mockDocumentContainer.Object, - sqlQuerySpec: new SqlQuerySpec("SELECT * FROM c ORDER BY c._ts"), - targetRanges: new List() { new FeedRangeEpk(new Range(min: "A", max: "B", isMinInclusive: true, isMaxInclusive: false)) }, - partitionKey: null, - orderByColumns: new List() - { - new OrderByColumn("_ts", SortOrder.Ascending) - }, - queryPaginationOptions: new QueryPaginationOptions(pageSizeHint: 10), - maxConcurrency: 10, - cancellationToken: default, - continuationToken: CosmosArray.Create( - new List() + foreach ((string serializedToken, CosmosElement element) in tokens) + { + ParallelContinuationToken parallelContinuationToken = new ParallelContinuationToken( + token: serializedToken, + range: new Documents.Routing.Range("A", "B", true, false)); + + OrderByContinuationToken orderByContinuationToken = new OrderByContinuationToken( + parallelContinuationToken, + new List() { new OrderByItem(CosmosObject.Create(new Dictionary() { { "item", element} })) }, + rid: "rid", + skipCount: 42, + filter: "filter"); + + TryCatch monadicCreate = OrderByCrossPartitionQueryPipelineStage.MonadicCreate( + documentContainer: mockDocumentContainer.Object, + sqlQuerySpec: new SqlQuerySpec("SELECT * FROM c ORDER BY c.item"), + targetRanges: new List() { new FeedRangeEpk(new Range(min: "A", max: "B", isMinInclusive: true, isMaxInclusive: false)) }, + partitionKey: null, + orderByColumns: new List() { + new OrderByColumn("item", SortOrder.Ascending) + }, + queryPaginationOptions: new QueryPaginationOptions(pageSizeHint: 10), + maxConcurrency: 10, + cancellationToken: default, + continuationToken: CosmosArray.Create( + new List() + { OrderByContinuationToken.ToCosmosElement(orderByContinuationToken) - })); - Assert.IsTrue(monadicCreate.Succeeded); + })); + Assert.IsTrue(monadicCreate.Succeeded); + } + + foreach ((string token1, CosmosElement element1) in tokens) + { + foreach ((string token2, CosmosElement element2) in tokens) + { + ParallelContinuationToken parallelContinuationToken1 = new ParallelContinuationToken( + token: $"[{token1}, {token2}]", + range: new Documents.Routing.Range("A", "B", true, false)); + + OrderByContinuationToken orderByContinuationToken = new OrderByContinuationToken( + parallelContinuationToken1, + new List() + { + new OrderByItem(CosmosObject.Create(new Dictionary(){ { "item1", element1 } })), + new OrderByItem(CosmosObject.Create(new Dictionary(){ { "item2", element2 } })) + }, + rid: "rid", + skipCount: 42, + filter: "filter"); + + TryCatch monadicCreate = OrderByCrossPartitionQueryPipelineStage.MonadicCreate( + documentContainer: mockDocumentContainer.Object, + sqlQuerySpec: new SqlQuerySpec("SELECT * FROM c ORDER BY c.item1, c.item2"), + targetRanges: new List() + { + new FeedRangeEpk(new Range(min: "A", max: "B", isMinInclusive: true, isMaxInclusive: false)), + new FeedRangeEpk(new Range(min: "B", max: "C", isMinInclusive: true, isMaxInclusive: false)), + }, + partitionKey: null, + orderByColumns: new List() + { + new OrderByColumn("item1", SortOrder.Ascending), + new OrderByColumn("item2", SortOrder.Ascending) + }, + queryPaginationOptions: new QueryPaginationOptions(pageSizeHint: 10), + maxConcurrency: 10, + cancellationToken: default, + continuationToken: CosmosArray.Create( + new List() + { + OrderByContinuationToken.ToCosmosElement(orderByContinuationToken) + })); + Assert.IsTrue(monadicCreate.Succeeded); + } + } } [TestMethod] public void MonadicCreate_MultipleOrderByContinuationToken() { Mock mockDocumentContainer = new Mock(); + IReadOnlyList<(string serializedToken, CosmosElement element)> tokens = TokenTestData(); - ParallelContinuationToken parallelContinuationToken1 = new ParallelContinuationToken( - token: "asdf", - range: new Documents.Routing.Range("A", "B", true, false)); - - OrderByContinuationToken orderByContinuationToken1 = new OrderByContinuationToken( - parallelContinuationToken1, - new List() { new OrderByItem(CosmosObject.Create(new Dictionary() { { "item", CosmosString.Create("asdf") } })) }, - rid: "rid", - skipCount: 42, - filter: "filter"); - - ParallelContinuationToken parallelContinuationToken2 = new ParallelContinuationToken( - token: "asdf", - range: new Documents.Routing.Range("B", "C", true, false)); - - OrderByContinuationToken orderByContinuationToken2 = new OrderByContinuationToken( - parallelContinuationToken2, - new List() { new OrderByItem(CosmosObject.Create(new Dictionary() { { "item", CosmosString.Create("asdf") } })) }, - rid: "rid", - skipCount: 42, - filter: "filter"); - - TryCatch monadicCreate = OrderByCrossPartitionQueryPipelineStage.MonadicCreate( - documentContainer: mockDocumentContainer.Object, - sqlQuerySpec: new SqlQuerySpec("SELECT * FROM c ORDER BY c._ts"), - targetRanges: new List() - { - new FeedRangeEpk(new Range(min: "A", max: "B", isMinInclusive: true, isMaxInclusive: false)), - new FeedRangeEpk(new Range(min: "B", max: "C", isMinInclusive: true, isMaxInclusive: false)), - }, - partitionKey: null, - orderByColumns: new List() + foreach((string token1, CosmosElement element1) in tokens) + { + foreach ((string token2, CosmosElement element2) in tokens) { - new OrderByColumn("_ts", SortOrder.Ascending) - }, - queryPaginationOptions: new QueryPaginationOptions(pageSizeHint: 10), - maxConcurrency: 10, - cancellationToken: default, - continuationToken: CosmosArray.Create( - new List() - { - OrderByContinuationToken.ToCosmosElement(orderByContinuationToken1), - OrderByContinuationToken.ToCosmosElement(orderByContinuationToken2) - })); - Assert.IsTrue(monadicCreate.Succeeded); + ParallelContinuationToken parallelContinuationToken1 = new ParallelContinuationToken( + token: token1, + range: new Documents.Routing.Range("A", "B", true, false)); + + OrderByContinuationToken orderByContinuationToken1 = new OrderByContinuationToken( + parallelContinuationToken1, + new List() { new OrderByItem(CosmosObject.Create(new Dictionary() { { "item", element1 } })) }, + rid: "rid", + skipCount: 42, + filter: "filter"); + + ParallelContinuationToken parallelContinuationToken2 = new ParallelContinuationToken( + token: token2, + range: new Documents.Routing.Range("B", "C", true, false)); + + OrderByContinuationToken orderByContinuationToken2 = new OrderByContinuationToken( + parallelContinuationToken2, + new List() { new OrderByItem(CosmosObject.Create(new Dictionary() { { "item", element2 } })) }, + rid: "rid", + skipCount: 42, + filter: "filter"); + + TryCatch monadicCreate = OrderByCrossPartitionQueryPipelineStage.MonadicCreate( + documentContainer: mockDocumentContainer.Object, + sqlQuerySpec: new SqlQuerySpec("SELECT * FROM c ORDER BY c.item"), + targetRanges: new List() + { + new FeedRangeEpk(new Range(min: "A", max: "B", isMinInclusive: true, isMaxInclusive: false)), + new FeedRangeEpk(new Range(min: "B", max: "C", isMinInclusive: true, isMaxInclusive: false)), + }, + partitionKey: null, + orderByColumns: new List() + { + new OrderByColumn("item", SortOrder.Ascending) + }, + queryPaginationOptions: new QueryPaginationOptions(pageSizeHint: 10), + maxConcurrency: 10, + cancellationToken: default, + continuationToken: CosmosArray.Create( + new List() + { + OrderByContinuationToken.ToCosmosElement(orderByContinuationToken1), + OrderByContinuationToken.ToCosmosElement(orderByContinuationToken2) + })); + Assert.IsTrue(monadicCreate.Succeeded); + } + } } [TestMethod] diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs index c72eaad251..c6195deec8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceTests.cs @@ -37,6 +37,23 @@ public void TestRootTrace() Assert.IsTrue(rootTrace.Duration > TimeSpan.Zero); } + [TestMethod] + public void TestAddChild() + { + Trace oneChild = Trace.GetRootTrace(name: "OneChild"); + Trace twoChild = Trace.GetRootTrace(name: "TwoChild"); + Trace rootTrace; + using (rootTrace = Trace.GetRootTrace(name: "RootTrace")) + { + rootTrace.AddChild(oneChild); + rootTrace.AddChild(twoChild); + } + + Assert.AreEqual(2, rootTrace.Children.Count); + Assert.AreEqual(oneChild, rootTrace.Children[0]); + Assert.AreEqual(twoChild, rootTrace.Children[1]); + } + [TestMethod] public void TestTraceChildren() { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs index 7901bd162a..d0227143f1 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Tracing/TraceWriterBaselineTests.cs @@ -822,7 +822,7 @@ public override void SerializeAsXml(XmlWriter xmlWriter) private sealed class TraceForBaselineTesting : ITrace { private readonly Dictionary data; - private readonly List children; + private readonly List children; public TraceForBaselineTesting( string name, @@ -834,7 +834,7 @@ public TraceForBaselineTesting( this.Level = level; this.Component = component; this.Parent = parent; - this.children = new List(); + this.children = new List(); this.data = new Dictionary(); } @@ -880,10 +880,15 @@ public ITrace StartChild(string name, [CallerMemberName] string memberName = "", public ITrace StartChild(string name, TraceComponent component, TraceLevel level, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { TraceForBaselineTesting child = new TraceForBaselineTesting(name, level, component, parent: this); - this.children.Add(child); + this.AddChild(child); return child; } + public void AddChild(ITrace trace) + { + this.children.Add(trace); + } + public static TraceForBaselineTesting GetRootTrace() { return new TraceForBaselineTesting("Trace For Baseline Testing", TraceLevel.Info, TraceComponent.Unknown, parent: null); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs index 23efdd3707..b67f9b88db 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/MockDocumentClient.cs @@ -260,7 +260,7 @@ private void Init() this.MockGlobalEndpointManager = new Mock(this, new ConnectionPolicy()); this.MockGlobalEndpointManager.Setup(gep => gep.ResolveServiceEndpoint(It.IsAny())).Returns(new Uri("http://localhost")); - this.MockGlobalEndpointManager.Setup(gep => gep.RefreshLocationAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); + this.MockGlobalEndpointManager.Setup(gep => gep.InitializeAccountPropertiesAndStartBackgroundRefresh(It.IsAny())); SessionContainer sessionContainer = new SessionContainer(this.ServiceEndpoint.Host); this.sessionContainer = sessionContainer; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/ToDoActivity.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/ToDoActivity.cs new file mode 100644 index 0000000000..4ac222fa6c --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Utils/ToDoActivity.cs @@ -0,0 +1,32 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Tests +{ + using Newtonsoft.Json; + + public class ToDoActivity + { + [JsonProperty(propertyName: "id")] + public string Id { get; set; } + [JsonProperty(propertyName: "pk")] + public string Pk { get; set; } + + public override bool Equals(object obj) + { + if (!(obj is ToDoActivity input)) + { + return false; + } + + return string.Equals(this.Id, input.Id) + && string.Equals(this.Pk, input.Pk); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/README.md b/README.md index dd0893e517..7a7ae88534 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Build Status](https://cosmos-db-sdk-public.visualstudio.com/cosmos-db-sdk-public/_apis/build/status/azure-cosmos-dotnet-v3?branchName=master)](https://cosmos-db-sdk-public.visualstudio.com/cosmos-db-sdk-public/_build/latest?definitionId=41&branchName=master) +[![NuGet](https://img.shields.io/nuget/v/Microsoft.Azure.Cosmos.svg)](https://www.nuget.org/packages/Microsoft.Azure.Cosmos) +[![NuGet Prerelease](https://img.shields.io/nuget/vpre/Microsoft.Azure.Cosmos.svg)](https://www.nuget.org/packages/Microsoft.Azure.Cosmos) # Microsoft Azure Cosmos DB .NET SDK Version 3 @@ -73,4 +74,3 @@ provided by the bot. You will only need to do this once across all repos using o This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - diff --git a/azure-pipelines-functional.yml b/azure-pipelines-functional.yml index 42de771703..355db29ba7 100644 --- a/azure-pipelines-functional.yml +++ b/azure-pipelines-functional.yml @@ -14,7 +14,7 @@ pr: variables: DebugArguments: ' --filter "TestCategory!=Quarantine" --verbosity normal ' ReleaseArguments: ' --filter "TestCategory!=Quarantine" --verbosity normal ' - VmImage: vs2017-win2016 # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops + VmImage: windows-latest # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops jobs: diff --git a/azure-pipelines-nightly.yml b/azure-pipelines-nightly.yml index 10de80b127..8db3ff01e6 100644 --- a/azure-pipelines-nightly.yml +++ b/azure-pipelines-nightly.yml @@ -14,7 +14,7 @@ schedules: variables: ReleaseArguments: ' --filter "TestCategory!=Quarantine" --verbosity normal ' - VmImage: vs2017-win2016 # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops + VmImage: windows-latest # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops BuildConfiguration: Release IsNightly: true diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3266d51894..a06b5c2a18 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,7 +13,7 @@ pr: variables: DebugArguments: ' --filter "TestCategory!=Quarantine & TestCategory!=Functional" --verbosity normal ' ReleaseArguments: ' --filter "TestCategory!=Quarantine & TestCategory!=Functional" --verbosity normal ' - VmImage: vs2017-win2016 # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops + VmImage: windows-latest # https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops jobs: diff --git a/changelog.md b/changelog.md index 0f976fb624..fcf0383af8 100644 --- a/changelog.md +++ b/changelog.md @@ -3,14 +3,49 @@ Preview features are treated as a separate branch and will not be included in th The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### [3.19.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.19.0) - 2021-05-25 -### [3.19.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.19.0-preview) - 2021-04-23 +#### Added +- [#2482](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2482) Azure Active Directory: Adds CosmosClient TokenCredential APIs +- [#2440](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2440) Performance: Adds Bulk optimizations to reduce allocations and improves async task handling +- [#2447](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2447) Availability: Adds account refresh logic on gateway outage instead of waiting on 5min background refresh +- [#2493](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2493) NullReferenceException: Adds logic to append the CosmosDiagnostics to NullReferenceExceptions +- [#2465](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2465) ObjectDisposedException: Adds logic to append the CosmosDiagnostics to ObjectDisposedException +- [#2390](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2390) Bulk: Adds retry for patch operations when request is to large +- [#2487](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2487) UserAgent: Adds flag to user agent to show if region failover is configured + +#### Fixed +- [#2451](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2451) Query: Fixes native c# parser not recognizing query with multiple IN statements. Introduced in 3.13.0 PR [#1743](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1743) +- [#2451](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2455) Bulk: Fixes diagnostic traces by removing redundant info and adding correct retry context. Introduced in 3.17.0 PR [#2097](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2097) +- [#2460](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2460) Permission: Fixes documentation on resource token range limit. (Thanks to arorainms) +- [#2490](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2490) Change Feed: Fixes CancellationToken to be honored. Introduced in 3.15.0 PR [#1933](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1933) +- [#2483](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2483) Availability: Fixes the get account information to stop the background refresh after CosmosClient is disposed. Introduced in 3.18.0 PR [#2355](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2355) +- [#2481](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2481) Azure Active Directory: Fixes token refresh interval, exception handling, and retry logic +- [#2474](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2474) Change Feed: Fixes exceptions generating "Change Feed should always have a next page". Introduced in 3.15.0 PR [#1933](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1933) +- [#2498](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2498) Diagnostics: Fixes default setting in Consistency Config Serialization. Introduced in 3.18.0 PR [#2250](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2250) + +### [3.19.0-preview1](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.19.0-preview1) - 2021-05-17 #### Added -- [#2308](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2308) Dedicated Gateway: Adds MaxIntegratedCacheStaleness to Item and Query Request Options -- [#2371](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2371) Request Options : Adds delegate on request options to access and add headers +- [#2398](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2398) Patch : Adds TrySerializeValueParameter method for PatchOperation +- [#2440](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2440) Performance: Adds Bulk optimizations to reduce allocations and improves async task handling +- [#2447](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2447) Availability: Adds account refresh logic on gateway outage instead of waiting on 5min background refresh +- [#2449](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2449) Client encryption: Adds PolicyFormatVersion and validation that partition key paths are not encrypted + +#### Fixed +- [#2451](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2451) Query: Fixes native c# parser not recognizing query with multiple IN statements. Introduced in 3.13.0 PR [#1743](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1743) +- [#2451](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2455) Bulk: Fixes diagnostic traces by removing redundant info and adding correct retry context. Introduced in 3.17.0 PR [#2097](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2097) +- [#2460](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2460) Permission: Fixes documentation on resource token range limit. (Thanks to arorainms) -### [3.18.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.18.0) - 2021-04-23 +### [3.19.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.19.0-preview) - 2021-04-27 + +#### Added +- [#2308](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2308) & [#2425](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2425) Dedicated Gateway: Adds MaxIntegratedCacheStaleness to Item and Query Request Options +- [#2371](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2371) Request Options : Adds delegate on request options to access and add headers +- [#2398](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2398) Patch : Adds TrySerializeValueParameter and makes PatchOperation internal since it is not used in any public API +- [#2331](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2331) ChangeFeedProcessor: Adds support for manual checkpoint, context, and stream + +### [3.18.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.18.0) - 2021-04-26 #### Added - [#2324](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2324) Diagnostics: Adds all http requests to diagnostics @@ -36,7 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2383](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2383) Availability: Fixes CancellationToken evaluation during failover which could prevent necessary SDK refreshes to occur - [#2376](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2376) Diagnostics: Fixes invalid nesting and handler names in ITrace. Introduced in 3.17.0 PR [#2097](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2097) - [#2286](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2286) Diagnostics: Fixes regression in ITrace where direct operation diagnostics were not included in exception scenarios. Introduced in 3.17.0 PR [#2097](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2097) - +- [#2424](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/2424) Query: Fixes TaskCanceledException being converted to InternalServerError and not including diagnostics on most exceptions. Introduced in 3.14.0 PR [#1812](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1812) ### [3.18.0-preview](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.18.0-preview) - 2021-03-18 diff --git a/templates/build-preview.yml b/templates/build-preview.yml index 11d2ecb4d9..ed3c4c4b2b 100644 --- a/templates/build-preview.yml +++ b/templates/build-preview.yml @@ -27,6 +27,25 @@ jobs: arguments: -p:Optimize=true -p:IsPreview=true versioningScheme: OFF +- job: + displayName: Encryption Project Ref SDK Preview ${{ parameters.BuildConfiguration }} + pool: + vmImage: ${{ parameters.VmImage }} + + steps: + - checkout: self # self represents the repo where the initial Pipelines YAML file was found + clean: true # if true, execute `execute git clean -ffdx && git reset --hard HEAD` before fetching + + - task: DotNetCoreCLI@2 + displayName: Build Microsoft.Azure.Cosmos.Encryption Project Ref + inputs: + command: build + configuration: $(parameters.BuildConfiguration) + nugetConfigPath: NuGet.config + projects: Microsoft.Azure.Cosmos.sln + arguments: -p:Optimize=true -p:IsPreview=true;SdkProjectRef=true + versioningScheme: OFF + - job: displayName: Preview Tests ${{ parameters.BuildConfiguration }} pool: diff --git a/templates/build-test.yml b/templates/build-test.yml index dc76da652e..8482c62030 100644 --- a/templates/build-test.yml +++ b/templates/build-test.yml @@ -47,14 +47,21 @@ jobs: clean: true # if true, execute `execute git clean -ffdx && git reset --hard HEAD` before fetching - task: DotNetCoreCLI@2 - displayName: Microsoft.Azure.Cosmos.PerformanceTests + displayName: Microsoft.Azure.Cosmos.PerformanceTests - Build + condition: succeeded() + inputs: + command: build + projects: 'Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/*.csproj' + arguments: -c Release + nugetConfigPath: NuGet.config + - task: DotNetCoreCLI@2 + displayName: Microsoft.Azure.Cosmos.PerformanceTests - Run condition: succeeded() inputs: command: run projects: 'Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Performance.Tests/*.csproj' - arguments: --configuration ${{ parameters.BuildConfiguration }} /p:OS=${{ parameters.OS }} -c Release --framework netcoreapp3.1 --allCategories=GateBenchmark -- -j Medium -m --BaselineValidation + arguments: --configuration ${{ parameters.BuildConfiguration }} /p:OS=${{ parameters.OS }} --no-restore -c Release --framework netcoreapp3.1 --allCategories=GateBenchmark -- -j Medium -m --BaselineValidation publishTestResults: true - nugetConfigPath: NuGet.config testRunTitle: Microsoft.Azure.Cosmos.PerformanceTests - job: