<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading.Tasks;
    using System.Web;
    using Microsoft.Cloud.Metrics.Client.Query;
    using Microsoft.Cloud.Metrics.Client.Utility;
    using Microsoft.Online.Metrics.Serialization;
    using Microsoft.Online.Metrics.Serialization.Configuration;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// <summary>
    /// The metrics reader class to read metrics data as well as metrics metadata.
    /// </summary>
    public sealed class MetricReader : IMetricReader
    {
        /// <summary>
        /// When a dimension has this value, it is not a pre-condition but it is part of the pre-aggregation.
        /// </summary>
        public const string ContextualHintingWildcardValue = "{{*}}";

#pragma warning disable SA1401 // Fields must be private
        /// <summary>
        /// The relative URL for metrics data
        /// </summary>
        public readonly string DataRelativeUrl;

        /// <summary>
        /// The relative URL for metrics meta-data a.k.a. hinting data
        /// </summary>
        public readonly string MetaDataRelativeUrl;

        /// <summary>
        /// The relative URL for metrics meta-data a.k.a. hinting data V2.
        /// </summary>
        public readonly string MetaDataRelativeUrlV2;

        /// <summary>
        /// The relative Url for distributed query.
        /// </summary>
        public readonly string DistributedQueryRelativeUrl;

        /// <summary>
        /// The query service relative URL.
        /// </summary>
        public readonly string QueryServiceRelativeUrl;
#pragma warning restore SA1401 // Fields must be private

        private const int MillisecondsPerMinute = 60000;

        /// <summary>
        /// The empty string array.
        /// </summary>
        private static readonly string[] EmptyStringArray = new string[0];

        /// <summary>
        /// The empty pre-aggregate configurations.
        /// </summary>
        private static readonly List<PreAggregateConfiguration> EmptyPreAggregateConfigurations = new List<PreAggregateConfiguration>();

        /// <summary>
        /// The HTTP client instance.
        /// </summary>
        private readonly HttpClient httpClient;

        /// <summary>
        /// The connection information.
        /// </summary>
        private readonly ConnectionInfo connectionInfo;

        /// <summary>
        /// The metric configuration manager.
        /// </summary>
        private readonly MetricConfigurationManager metricConfigurationManager;

        /// <summary>
        /// The string identifying client.
        /// </summary>
        private readonly string clientId;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricReader"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        /// <param name="clientId">The string identifying client.</param>
        public MetricReader(ConnectionInfo connectionInfo, string clientId = "ClientAPI")
            : this(connectionInfo, HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo), clientId)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricReader"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        /// <param name="authHeaderValue">The auth header.</param>
        /// <param name="clientId">The string identifying client.</param>
        public MetricReader(ConnectionInfo connectionInfo, string authHeaderValue, string clientId = "ClientAPI")
            : this(connectionInfo, HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo, authHeaderValue), clientId)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricReader"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        /// <param name="httpClient">The http client with auth info.</param>
        /// <param name="clientId">The string identifying client.</param>
        internal MetricReader(ConnectionInfo connectionInfo, HttpClient httpClient, string clientId = "ClientAPI")
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException("connectionInfo");
            }

            this.connectionInfo = connectionInfo;
            this.DataRelativeUrl = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.DataRelativeUrl);
            this.MetaDataRelativeUrl = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.MetaDataRelativeUrl);
            this.MetaDataRelativeUrlV2 = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.MetaDataRelativeUrlV2);
            this.DistributedQueryRelativeUrl = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.DistributedQueryRelativeUrl);
            this.QueryServiceRelativeUrl = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.QueryServiceRelativeUrl);
            this.clientId = clientId;
            this.httpClient = httpClient;
            this.metricConfigurationManager = new MetricConfigurationManager(this.connectionInfo);
        }

        /// <summary>
        /// Gets the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="definition">The time series definition.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definition"/>.
        /// </returns>
        public async Task<TimeSeries<MetricIdentifier, double?>> GetTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            TimeSeriesDefinition<MetricIdentifier> definition)
        {
            if (definition == null)
            {
                throw new ArgumentNullException("definition");
            }

            var series = await this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, samplingType, definition).ConfigureAwait(false);
            return series.First();
        }

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        public Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            params TimeSeriesDefinition<MetricIdentifier>[] definitions)
        {
            return this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, samplingType, definitions.AsEnumerable());
        }

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        public Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions)
        {
            return this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, new[] { samplingType }, definitions);
        }

        /// <summary>
        /// Gets the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="definition">The time series definition.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definition"/>.
        /// </returns>
        /// <remarks>This API uses <see cref="AggregationType.Automatic"/> by default and other overloads are available for specific <see cref="AggregationType"/>.</remarks>
        public async Task<TimeSeries<MetricIdentifier, double?>> GetTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            int seriesResolutionInMinutes,
            TimeSeriesDefinition<MetricIdentifier> definition)
        {
            if (definition == null)
            {
                throw new ArgumentNullException("definition");
            }

            var series = await this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, samplingType, seriesResolutionInMinutes, definition).ConfigureAwait(false);
            return series.First();
        }

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        /// <remarks>This API uses <see cref="AggregationType.Automatic"/> by default and other overloads are available for specific <see cref="AggregationType"/>.</remarks>
        public Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            int seriesResolutionInMinutes,
            params TimeSeriesDefinition<MetricIdentifier>[] definitions)
        {
            return this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, samplingType, seriesResolutionInMinutes, definitions.AsEnumerable());
        }

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        /// <remarks>This API uses <see cref="AggregationType.Automatic"/> by default and other overloads are available for specific <see cref="AggregationType"/>.</remarks>
        public Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            int seriesResolutionInMinutes,
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions)
        {
            return this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, new[] { samplingType }, definitions, seriesResolutionInMinutes);
        }

        /// <summary>
        /// Gets time series with multiple sampling types.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="definition">The time series definition.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="aggregationType">The aggregation function used to reduce the resolution of the returned series.</param>
        /// <returns>
        /// The time series for the given <paramref name="definition" />.
        /// </returns>
        public async Task<TimeSeries<MetricIdentifier, double?>> GetTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType[] samplingTypes,
            TimeSeriesDefinition<MetricIdentifier> definition,
            int seriesResolutionInMinutes = 1,
            AggregationType aggregationType = AggregationType.Automatic)
        {
            if (definition == null)
            {
                throw new ArgumentNullException(nameof(definition));
            }

            var series = await this.GetMultipleTimeSeriesAsync(startTimeUtc, endTimeUtc, samplingTypes, new[] { definition }, seriesResolutionInMinutes, aggregationType).ConfigureAwait(false);
            return series.FirstOrDefault();
        }

        /// <summary>
        /// Gets a list of the time series, each with multiple sampling types.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="aggregationType">The aggregation function used to reduce the resolution of the returned series.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        public async Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType[] samplingTypes,
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions,
            int seriesResolutionInMinutes = 1,
            AggregationType aggregationType = AggregationType.Automatic)
        {
            if (definitions == null)
            {
                throw new ArgumentNullException("definitions");
            }

            if (samplingTypes == null || samplingTypes.Length == 0)
            {
                throw new ArgumentException("cannot be null or empty", nameof(samplingTypes));
            }

            if (seriesResolutionInMinutes < SerializationConstants.DefaultSeriesResolutionInMinutes)
            {
                throw new ArgumentException($"{seriesResolutionInMinutes} must be >= {SerializationConstants.DefaultSeriesResolutionInMinutes}", nameof(samplingTypes));
            }

            List<TimeSeriesDefinition<MetricIdentifier>> definitionList = definitions.ToList();

            if (definitionList.Count == 0)
            {
                throw new ArgumentException("The count of 'definitions' is 0.");
            }

            if (definitionList.Any(d => d == null))
            {
                throw new ArgumentException("At least one of definitions are null.");
            }

            if (startTimeUtc > endTimeUtc)
            {
                throw new ArgumentException(string.Format("startTimeUtc [{0}] must be <= endTimeUtc [{1}]", startTimeUtc, endTimeUtc));
            }

            NormalizeTimeRange(ref startTimeUtc, ref endTimeUtc);

            foreach (var timeSeriesDefinition in definitionList)
            {
                timeSeriesDefinition.SamplingTypes = samplingTypes;
                timeSeriesDefinition.StartTimeUtc = startTimeUtc;
                timeSeriesDefinition.EndTimeUtc = endTimeUtc;
                timeSeriesDefinition.SeriesResolutionInMinutes = seriesResolutionInMinutes;
                timeSeriesDefinition.AggregationType = aggregationType;
            }

            return await this.GetMultipleTimeSeriesAsync(definitionList).ConfigureAwait(false);
        }

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions"/>.
        /// </returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        public async Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(IList<TimeSeriesDefinition<MetricIdentifier>> definitions)
        {
            using (var response = await this.GetMultipleTimeSeriesAsync(definitions, MetricQueryResponseDeserializer.CurrentVersion, returnMetricNames: false).ConfigureAwait(false))
            {
                using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                {
                    return MetricQueryResponseDeserializer.Deserialize(stream, definitions.ToArray()).Item2;
                }
            }
        }

        /// <summary>
        /// Gets the list of namespaces for the <paramref name="monitoringAccount"/>.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <returns>The list of namespaces for the <paramref name="monitoringAccount"/>.</returns>
        public async Task<IReadOnlyList<string>> GetNamespacesAsync(string monitoringAccount)
        {
            if (string.IsNullOrWhiteSpace(monitoringAccount))
            {
                throw new ArgumentException("monitoringAccount is null or empty.");
            }

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace",
                this.connectionInfo.GetEndpoint(monitoringAccount),
                this.MetaDataRelativeUrl,
                monitoringAccount);

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Get,
                this.httpClient,
                monitoringAccount,
                this.MetaDataRelativeUrl,
                null,
                this.clientId).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<string[]>(response.Item1);
        }

        /// <summary>
        /// Gets the list of metric names for the <paramref name="monitoringAccount" /> and <paramref name="metricNamespace" />.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <returns>
        /// The list of metric names for the <paramref name="monitoringAccount" /> and <paramref name="metricNamespace" />.
        /// </returns>
        public async Task<IReadOnlyList<string>> GetMetricNamesAsync(string monitoringAccount, string metricNamespace)
        {
            if (string.IsNullOrWhiteSpace(monitoringAccount))
            {
                throw new ArgumentException("monitoringAccount is null or empty.");
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentException("metricNamespace is null or empty.");
            }

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric",
                this.connectionInfo.GetEndpoint(monitoringAccount),
                this.MetaDataRelativeUrl,
                monitoringAccount,
                SpecialCharsHelper.EscapeTwice(metricNamespace));

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Get,
                this.httpClient,
                monitoringAccount,
                this.MetaDataRelativeUrl,
                null,
                this.clientId).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<string[]>(response.Item1);
        }

        /// <summary>
        /// Gets the list of dimension names for the <paramref name="metricId" />.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <returns>
        /// The list of dimension names for the <paramref name="metricId" />.
        /// </returns>
        public async Task<IReadOnlyList<string>> GetDimensionNamesAsync(MetricIdentifier metricId)
        {
            var config = await this.metricConfigurationManager.Get(metricId).ConfigureAwait(false);

            return config.DimensionConfigurations == null ? EmptyStringArray : config.DimensionConfigurations.Select(d => d.Id).ToArray();
        }

        /// <summary>
        /// Gets the list of pre-aggregate configurations for the <paramref name="metricId" />.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <returns>
        /// The list of pre-aggregate configurations for the <paramref name="metricId" />.
        /// </returns>
        public async Task<IReadOnlyList<PreAggregateConfiguration>> GetPreAggregateConfigurationsAsync(MetricIdentifier metricId)
        {
            var config = await this.metricConfigurationManager.Get(metricId).ConfigureAwait(false);

            return config.PreAggregations ?? EmptyPreAggregateConfigurations;
        }

        /// <summary>
        /// Gets the known dimension combinations.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <returns>Time series definitions with  known dimension combinations.</returns>
        public Task<IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>>> GetKnownTimeSeriesDefinitionsAsync(
            MetricIdentifier metricId,
            params DimensionFilter[] dimensionFilters)
        {
            return this.GetKnownTimeSeriesDefinitionsAsync(metricId, dimensionFilters.AsEnumerable());
        }

        /// <summary>
        /// Gets the known dimension combinations.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <returns>Time series definitions with  known dimension combinations.</returns>
        public async Task<IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>>> GetKnownTimeSeriesDefinitionsAsync(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters)
        {
            return await this.GetKnownTimeSeriesDefinitionsAsync(metricId, dimensionFilters, DateTime.MinValue, DateTime.MaxValue).ConfigureAwait(false);
        }

        /// <summary>
        /// Gets the known dimension combinations.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">Start time for evaluating dimension values.</param>
        /// <param name="endTimeUtc">End time for evaluating dimension values.</param>
        /// <param name="newCombinationsOnly">
        /// If true only combinations which were added into memory in this session of hinting system after fromTimeUtc.
        /// This flag does *not* guarantee that only new combinations will be returned
        /// It is more of a hint to the hinting system to try to give only new combinations in given time range.
        /// </param>
        /// <returns>Time series definitions with  known dimension combinations.</returns>
        public async Task<IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>>> GetKnownTimeSeriesDefinitionsAsync(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            bool newCombinationsOnly = false)
        {
            metricId.Validate();

            var dimensionNamesAndConstraints = GetDimensionNamesAndConstraints(dimensionFilters);

            var url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metricName/{4}/startTimeUtcMillis/{5}/endTimeUtcMillis/{6}",
                this.connectionInfo.GetEndpoint(metricId.MonitoringAccount),
                this.MetaDataRelativeUrl,
                SpecialCharsHelper.EscapeTwice(metricId.MonitoringAccount),
                SpecialCharsHelper.EscapeTwice(metricId.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricId.MetricName),
                UnixEpochHelper.GetMillis(startTimeUtc),
                UnixEpochHelper.GetMillis(endTimeUtc));

            if (newCombinationsOnly)
            {
                url = string.Format("{0}/newOnly", url);
            }

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                metricId.MonitoringAccount,
                this.MetaDataRelativeUrl,
                dimensionNamesAndConstraints,
                this.clientId).ConfigureAwait(false);

            var deserializedResponse = JsonConvert.DeserializeObject<Tuple<List<string>, List<List<string>>>>(response.Item1);

            TimeSeriesDefinition<MetricIdentifier>[] results;
            if (deserializedResponse != null && deserializedResponse.Item1 != null && deserializedResponse.Item2 != null)
            {
                results = new TimeSeriesDefinition<MetricIdentifier>[deserializedResponse.Item2.Count];
                var preaggreate = deserializedResponse.Item1;

                for (var index = 0; index < deserializedResponse.Item2.Count; index++)
                {
                    var r = deserializedResponse.Item2[index];
                    var dimensionCombination = new KeyValuePair<string, string>[r.Count];
                    for (var i = 0; i < preaggreate.Count; i++)
                    {
                        dimensionCombination[i] = new KeyValuePair<string, string>(preaggreate[i], r[i]);
                    }

                    results[index] = new TimeSeriesDefinition<MetricIdentifier>(metricId, dimensionCombination);
                }
            }
            else
            {
                results = new TimeSeriesDefinition<MetricIdentifier>[0];
            }

            return results;
        }

        /// <summary>
        /// Gets the dimension values for <paramref name="dimensionName"/> satifying the <paramref name="dimensionFilters"/>
        /// and time range (<paramref name="startTimeUtc"/>, <paramref name="endTimeUtc"/>)
        /// </summary>
        /// <remarks>
        /// Time range resolution is day.
        /// </remarks>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">
        /// The dimension filters representing the pre-aggregate dimensions.
        /// Create an emtpy include filter for dimension with no filter values.
        /// Requested dimension should also be part of this and should be empty.
        /// </param>
        /// <param name="dimensionName">Name of the dimension for which values are requested.</param>
        /// <param name="startTimeUtc">Start time for evaluating dimension values.</param>
        /// <param name="endTimeUtc">End time for evaluating dimension values.</param>
        /// <returns>Dimension values for <paramref name="dimensionName"/>.</returns>
        public async Task<IReadOnlyList<string>> GetDimensionValuesAsync(
            MetricIdentifier metricId,
            List<DimensionFilter> dimensionFilters,
            string dimensionName,
            DateTime startTimeUtc,
            DateTime endTimeUtc)
        {
            metricId.Validate();

            if (dimensionFilters == null || dimensionFilters.Count == 0)
            {
                throw new ArgumentException("Dimension filters cannot be empty or null");
            }

            dimensionFilters.Sort((item1, item2) => string.Compare(item1.DimensionName, item2.DimensionName, StringComparison.OrdinalIgnoreCase));

            var url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}/startTimeUtcMillis/{5}/endTimeUtcMillis/{6}/dimension/{7}",
                this.connectionInfo.GetEndpoint(metricId.MonitoringAccount),
                this.MetaDataRelativeUrlV2,
                SpecialCharsHelper.EscapeTwice(metricId.MonitoringAccount),
                SpecialCharsHelper.EscapeTwice(metricId.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricId.MetricName),
                UnixEpochHelper.GetMillis(startTimeUtc),
                UnixEpochHelper.GetMillis(endTimeUtc),
                SpecialCharsHelper.EscapeTwice(dimensionName));

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                metricId.MonitoringAccount,
                this.MetaDataRelativeUrlV2,
                null,
                this.clientId,
                JsonConvert.SerializeObject(dimensionFilters)).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<List<string>>(response.Item1);
        }

        /// <summary>
        /// Gets the known dimension combinations that match the query criteria.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingType">Sampling type to use for this metric.</param>
        /// <param name="reducer">The reducing function to apply to the time series.</param>
        /// <param name="queryFilter">Filter criteria to enforce on the query.</param>
        /// <param name="includeSeries">Indicate whether or not to include the raw time series data in the result.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.</param>
        /// <returns>Time series definitions matching the query criteria.</returns>
        [Obsolete]
        public async Task<IReadOnlyList<IQueryResult>> GetFilteredDimensionValuesAsync(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            Reducer reducer,
            QueryFilter queryFilter,
            bool includeSeries,
            SelectionClause selectionClause = null,
            AggregationType aggregationType = AggregationType.Sum,
            long seriesResolutionInMinutes = 1)
        {
            SortedDictionary<string, Tuple<bool, IReadOnlyList<string>>> dimensionNamesAndConstraints;
            var query = this.BuildQueryParameters(metricId, dimensionFilters, startTimeUtc, endTimeUtc, samplingType, reducer, queryFilter, includeSeries, selectionClause, aggregationType, seriesResolutionInMinutes, out dimensionNamesAndConstraints);

            string path = string.Format(
                "{0}/monitoringAccount/{1}/metricNamespace/{2}/metric/{3}",
                this.DistributedQueryRelativeUrl,
                SpecialCharsHelper.EscapeTwice(metricId.MonitoringAccount),
                SpecialCharsHelper.EscapeTwice(metricId.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricId.MetricName));

            var builder = new UriBuilder(this.connectionInfo.GetEndpoint(metricId.MonitoringAccount))
            {
                Path = path,
                Query = query.ToString()
            };

            var response = await HttpClientHelper.GetResponse(
                builder.Uri,
                HttpMethod.Post,
                this.httpClient,
                metricId.MonitoringAccount,
                this.DistributedQueryRelativeUrl,
                dimensionNamesAndConstraints,
                this.clientId).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<QueryResult[]>(response.Item1);
        }

        /// <summary>
        /// Gets the known dimension combinations that match the query criteria.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingType">Sampling type to use for this metric.</param>
        /// <param name="reducer">The reducing function to apply to the time series.</param>
        /// <param name="queryFilter">Filter criteria to enforce on the query.</param>
        /// <param name="includeSeries">Indicate whether or not to include the raw time series data in the result.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.</param>
        /// <returns>Time series definitions matching the query criteria.</returns>
        [Obsolete]
        public async Task<QueryResultsList> GetFilteredDimensionValuesAsyncV2(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            Reducer reducer,
            QueryFilter queryFilter,
            bool includeSeries,
            SelectionClause selectionClause = null,
            AggregationType aggregationType = AggregationType.Sum,
            long seriesResolutionInMinutes = 1)
        {
            SortedDictionary<string, Tuple<bool, IReadOnlyList<string>>> dimensionNamesAndConstraints;
            var query = this.BuildQueryParameters(metricId, dimensionFilters, startTimeUtc, endTimeUtc, samplingType, reducer, queryFilter, includeSeries, selectionClause, aggregationType, seriesResolutionInMinutes, out dimensionNamesAndConstraints);

            string operation = $"{this.DistributedQueryRelativeUrl}/V2";

            string path = string.Format(
                "{0}/monitoringAccount/{1}/metricNamespace/{2}/metric/{3}",
                operation,
                SpecialCharsHelper.EscapeTwice(metricId.MonitoringAccount),
                SpecialCharsHelper.EscapeTwice(metricId.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricId.MetricName));

            var builder = new UriBuilder(this.connectionInfo.GetEndpoint(metricId.MonitoringAccount))
            {
                Path = path,
                Query = query.ToString()
            };

            var response = await HttpClientHelper.GetResponse(
                builder.Uri,
                HttpMethod.Post,
                this.httpClient,
                metricId.MonitoringAccount,
                operation,
                dimensionNamesAndConstraints,
                this.clientId).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<QueryResultsList>(response.Item1);
        }

        /// <summary>
        /// Gets the time series values that match the filtering criteria.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.  By default, all results are returned.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.  By default, automatic resolution is used (same as Jarvis UI).</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.  1 minute resolution (full resolution in MDM today) by default.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <param name="outputDimensionNames">The output dimension names.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.</param>
        /// <returns>
        /// Time series definitions matching the query criteria.
        /// </returns>
        [Obsolete]
        public async Task<IQueryResultListV3> GetFilteredDimensionValuesAsyncV3(
            MetricIdentifier metricId,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            IReadOnlyList<SamplingType> samplingTypes,
            SelectionClauseV3 selectionClause = null,
            AggregationType aggregationType = AggregationType.Automatic,
            long seriesResolutionInMinutes = 1,
            Guid? traceId = null,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false)
        {
            if (samplingTypes == null || samplingTypes.Count == 0)
            {
                throw new ArgumentException("One or more sampling types must be specified.");
            }

            if (selectionClause == null)
            {
                selectionClause = new SelectionClauseV3(new PropertyDefinition(PropertyAggregationType.Average, samplingTypes[0]), int.MaxValue, OrderBy.Undefined);
            }

            if (dimensionFilters == null)
            {
                throw new ArgumentNullException(nameof(dimensionFilters));
            }

            if (startTimeUtc > endTimeUtc)
            {
                throw new ArgumentException("Start time must be before end time.");
            }

            traceId = traceId ?? Guid.NewGuid();

            var request = new FilteredTimeSeriesQueryRequest(
                metricId,
                samplingTypes,
                dimensionFilters,
                startTimeUtc,
                endTimeUtc,
                (int)seriesResolutionInMinutes,
                aggregationType,
                selectionClause.PropertyDefinition,
                selectionClause.NumberOfResultsToReturn,
                selectionClause.OrderBy,
                false,
                outputDimensionNames,
                lastValueMode);

            var url = string.Format(
                "{0}{1}/v1/multiple/serializationVersion/{2}/maxCost/{3}?timeoutInSeconds={4}&returnRequestObjectOnFailure={5}",
                this.connectionInfo.GetMetricsDataQueryEndpoint(request.MetricIdentifier.MonitoringAccount).OriginalString,
                this.QueryServiceRelativeUrl,
                FilteredTimeSeriesQueryResponse.CurrentVersion,
                int.MaxValue,
                (int)this.connectionInfo.Timeout.TotalSeconds,
                false);

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                null, // TODO add support of monitoring account on server side and then pass it here
                null, // TODO add support of monitoring account on server side and pass operation here
                new[] { request },
                this.clientId,
                null,
                traceId,
                FilteredTimeSeriesQueryResponse.CurrentVersion).ConfigureAwait(false);

            using (HttpResponseMessage httpResponseMessage = response.Item2)
            {
                if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
                {
                    throw new MetricsClientException($"Request failed with HTTP Status Code: {httpResponseMessage.StatusCode}. TraceId: {traceId}.  Response: {response.Item1}");
                }

                IReadOnlyList<IFilteredTimeSeriesQueryResponse> results;
                using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
                {
                    results = FilteredQueryResponseDeserializer.Deserialize(stream);
                }

                if (results == null || results.Count == 0)
                {
                    throw new MetricsClientException($"Response is null or empty.  TraceId: {traceId}");
                }

                IFilteredTimeSeriesQueryResponse result = results[0];
                if (result.ErrorCode != FilteredTimeSeriesQueryResponseErrorCode.Success)
                {
                    throw new MetricsClientException(
                        $"Error occured processing the request.  Error code: {result.ErrorCode}. {result.DiagnosticInfo}",
                        null,
                        traceId.Value,
                        httpResponseMessage.StatusCode);
                }

                return new QueryResultListV3(result.StartTimeUtc, result.EndTimeUtc, result.TimeResolutionInMinutes, (IReadOnlyList<FilteredTimeSeries>)result.FilteredTimeSeriesList);
            }
        }

        /// <summary>
        /// Gets the time series values that match the filtering criteria.
        /// This version of code gets values one by one and is useful for scenarios where you do not want to
        /// keep the list of the results in memory and process them on the fly.
        /// Note: This API is for advanced scenarios only. Use it only when you fetch huge amounts of metrics from
        /// multiple stamps in parallel and face performance problems related to memory usage
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.  By default, all results are returned.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.  By default, automatic resolution is used (same as Jarvis UI).</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.  1 minute resolution (full resolution in MDM today) by default.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <param name="outputDimensionNames">The output dimension names.</param>
        /// <returns>
        /// The response message.
        /// </returns>
        public async Task<HttpResponseMessage> GetTimeSeriesStreamedAsync(
            MetricIdentifier metricId,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            IReadOnlyList<SamplingType> samplingTypes,
            SelectionClauseV3 selectionClause = null,
            AggregationType aggregationType = AggregationType.Automatic,
            long seriesResolutionInMinutes = 1,
            Guid? traceId = null,
            IReadOnlyList<string> outputDimensionNames = null)
        {
            if (samplingTypes == null || samplingTypes.Count == 0)
            {
                throw new ArgumentException("One or more sampling types must be specified.");
            }

            if (selectionClause == null)
            {
                selectionClause = new SelectionClauseV3(new PropertyDefinition(PropertyAggregationType.Average, samplingTypes[0]), int.MaxValue, OrderBy.Undefined);
            }

            if (dimensionFilters == null)
            {
                throw new ArgumentNullException(nameof(dimensionFilters));
            }

            if (startTimeUtc > endTimeUtc)
            {
                throw new ArgumentException("Start time must be before end time.");
            }

            traceId = traceId ?? Guid.NewGuid();

            var request = new FilteredTimeSeriesQueryRequest(
                metricId,
                samplingTypes,
                dimensionFilters,
                startTimeUtc,
                endTimeUtc,
                (int)seriesResolutionInMinutes,
                aggregationType,
                selectionClause.PropertyDefinition,
                selectionClause.NumberOfResultsToReturn,
                selectionClause.OrderBy,
                false,
                outputDimensionNames);

            var url = string.Format(
                "{0}{1}/v1/multiple/serializationVersion/{2}/maxCost/{3}?timeoutInSeconds={4}&returnRequestObjectOnFailure={5}",
                this.connectionInfo.GetMetricsDataQueryEndpoint(request.MetricIdentifier.MonitoringAccount).OriginalString,
                this.QueryServiceRelativeUrl,
                FilteredTimeSeriesQueryResponse.CurrentVersion,
                int.MaxValue,
                (int)this.connectionInfo.Timeout.TotalSeconds,
                false);

            Tuple<string, HttpResponseMessage> response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                null, // TODO add support of monitoring account on server side and then pass it here
                null, // TODO add support of monitoring account on server side and pass operation here
                new[] { request },
                this.clientId,
                null,
                traceId,
                FilteredTimeSeriesQueryResponse.CurrentVersion).ConfigureAwait(false);

            HttpResponseMessage httpResponseMessage = response.Item2;

            if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
            {
                throw new MetricsClientException($"Request failed with HTTP Status Code: {httpResponseMessage.StatusCode}. TraceId: {traceId}.  Response: {response.Item1}");
            }

            return httpResponseMessage;
        }

        /// <summary>
        /// Executes the given Kql-M query and returns results as a datatable.
        /// Datatable is represented by JArray where each element represents a row in the query result.
        ///
        /// For example:
        /// Input Query for last 1 hour at 1 minute resolution:
        /// metricNamespace("Metrics.Server").metric("ClientAggregatedMetricCount").dimensions("Datacenter") | project Average
        /// Output (will contain one row for each datapoint):
        /// [
        ///   {
        ///         "TimestampUtc": "02/04/2019 07:11:00",
        ///         "AccountName": "MetricTeamInternalMetrics",
        ///         "MetricNamespace": "Metrics.Server",
        ///         "MetricName": "ClientAggregatedMetricCount",
        ///         "Datacenter": "EastUS2",
        ///         "Average": 70.083851254134714
        ///    },
        ///    {
        ///         "TimestampUtc": "02/04/2019 07:12:00",
        ///         "AccountName": "MetricTeamInternalMetrics",
        ///         "MetricNamespace": "Metrics.Server",
        ///         "MetricName": "ClientAggregatedMetricCount",
        ///         "Datacenter": "EastUS2",
        ///         "Average": 67.305346411549351
        ///     }
        /// ]
        /// </summary>
        /// <param name="traceId">Trace id for end to end tracing purposes.</param>
        /// <param name="accountName">Account name for which query needs to be run.</param>
        /// <param name="queryString">KQL-M query.</param>
        /// <param name="startTimeUtc">Start time utc for the query.</param>
        /// <param name="endTimeUtc">End time utc for the query.</param>
        /// <returns>Result for query.</returns>
        public async Task<JArray> ExecuteKqlMQueryAsync(
            string traceId,
            string accountName,
            string queryString,
            DateTime startTimeUtc,
            DateTime endTimeUtc)
        {
            var queryLanguageRequest =
                new KqlMRequest(accountName, "[N/A]", "[N/A]", startTimeUtc, endTimeUtc, queryString);

            var url = string.Format(
                "{0}{1}query/v2/language/monitoringAccount/{2}",
                this.connectionInfo.GetEndpoint(accountName).OriginalString,
                this.connectionInfo.GetAuthRelativeUrl(string.Empty),
                accountName);

            Guid traceIdGuid;
            Guid? traceIdNullable = null;
            if (Guid.TryParse(traceId, out traceIdGuid))
            {
                traceIdNullable = traceIdGuid;
            }

            var response = await HttpClientHelper.GetResponse(
                url: new Uri(url),
                method: HttpMethod.Post,
                client: this.httpClient,
                monitoringAccount: accountName,
                operation: null,
                httpContent: queryLanguageRequest,
                clientId: this.clientId,
                serializedContent: null,
                traceId: traceIdNullable,
                serializationVersion: FilteredTimeSeriesQueryResponse.CurrentVersion).ConfigureAwait(false);

            using (HttpResponseMessage httpResponseMessage = response.Item2)
            {
                if (httpResponseMessage.StatusCode != HttpStatusCode.OK)
                {
                    throw new MetricsClientException($"Request failed with HTTP Status Code: {httpResponseMessage.StatusCode}. TraceId: {traceId}.  Response: {response.Item1}");
                }

                using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
                {
                    return QueryLanguageResponseToDatatable.GetResponseAsTable(stream);
                }
            }
        }

        /// <summary>
        /// Gets the time series values that match the filtering criteria.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.  By default, all results are returned.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.  By default, automatic resolution is used (same as Jarvis UI).</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.  1 minute resolution (full resolution in MDM today) by default.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <param name="outputDimensionNames">The output dimension names.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.</param>
        /// <returns>
        /// Time series results matching the query criteria.
        /// </returns>
        public Task<IQueryResultListV3> GetTimeSeriesAsync(
            MetricIdentifier metricId,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            IReadOnlyList<SamplingType> samplingTypes,
            SelectionClauseV3 selectionClause = null,
            AggregationType aggregationType = AggregationType.Automatic,
            long seriesResolutionInMinutes = 1,
            Guid? traceId = null,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false)
        {
            return this.GetFilteredDimensionValuesAsyncV3(
                metricId,
                dimensionFilters,
                startTimeUtc,
                endTimeUtc,
                samplingTypes,
                selectionClause,
                aggregationType,
                seriesResolutionInMinutes,
                traceId,
                outputDimensionNames,
                lastValueMode);
        }

        /// <summary>
        /// Returns all metric definitions for time series satisfying given set of filters in the given monitoring account.
        /// </summary>
        /// <remarks>
        /// QOS metrics, composite metrics and wild card metrics are not included in the result set.
        /// </remarks>
        /// <param name="monitoringAccount">Monitoring account name.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <returns>
        /// All metric definitions which has data for time series keys satisfy the given set of filters.
        /// </returns>
        public async Task<IReadOnlyList<MetricDefinitionV2>> GetMetricDefinitionsAsync(
            string monitoringAccount,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            Guid? traceId = null)
        {
            if (string.IsNullOrEmpty(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount), "Monitoring account cannot be null or empty");
            }

            if (dimensionFilters == null || dimensionFilters.Count == 0)
            {
                throw new ArgumentNullException(nameof(dimensionFilters), "Dimension filters cannot be null or empty");
            }

            if (startTimeUtc > endTimeUtc)
            {
                throw new ArgumentException(string.Format("Start time cannot be greater than end time. StartTime:{0}, EndTime:{1}", startTimeUtc, endTimeUtc));
            }

            bool hasAtleastOneDimensionValueFilter = false;
            foreach (var dimensionFilter in dimensionFilters)
            {
                if (dimensionFilter.DimensionValues.Count > 0)
                {
                    hasAtleastOneDimensionValueFilter = true;
                    break;
                }
            }

            if (!hasAtleastOneDimensionValueFilter)
            {
                throw new ArgumentException("Dimension filters need to have atleast one dimension with dimension values filters");
            }

            var dimensionNamesAndConstraints = GetDimensionNamesAndConstraints(dimensionFilters);

            var url = string.Format(
                "{0}{1}/metricDefinitions/monitoringAccount/{2}/startTimeUtcMillis/{3}/endTimeUtcMillis/{4}",
                this.connectionInfo.GetEndpoint(monitoringAccount),
                this.MetaDataRelativeUrl,
                SpecialCharsHelper.EscapeTwice(monitoringAccount),
                UnixEpochHelper.GetMillis(startTimeUtc),
                UnixEpochHelper.GetMillis(endTimeUtc));

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                monitoringAccount,
                this.MetaDataRelativeUrl,
                dimensionNamesAndConstraints,
                this.clientId,
                traceId: traceId).ConfigureAwait(false);

            var metricDefinitionsAsByteArray = await response.Item2.Content.ReadAsByteArrayAsync().ConfigureAwait(false);

            using (var ms = new MemoryStream(metricDefinitionsAsByteArray))
            {
                using (var reader = new BinaryReader(ms, Encoding.UTF8))
                {
                    reader.ReadByte();

                    List<string> tempList = new List<string>();
                    int numberOfMetadata = reader.ReadInt32();
                    var result = new List<MetricDefinitionV2>(numberOfMetadata);
                    for (int i = 0; i < numberOfMetadata; i++)
                    {
                        var monitoringAcct = reader.ReadString();
                        var metricNamespace = reader.ReadString();
                        var metricName = reader.ReadString();

                        var numOfDimensions = reader.ReadInt32();
                        for (int j = 0; j < numOfDimensions; j++)
                        {
                            tempList.Add(reader.ReadString());
                        }

                        var metadata = new MetricDefinitionV2(monitoringAcct, metricNamespace, metricName, tempList);
                        result.Add(metadata);
                        tempList.Clear();
                    }

                    return result;
                }
            }
        }

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="definitions">The time series definitions.</param>
        /// <param name="serializationVersion">The serialization version.</param>
        /// <param name="returnMetricNames">if set to <c>true</c>, return metric names in response.</param>
        /// <param name="traceId">The trace identifier.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        /// <exception cref="ArgumentNullException">If arguments provided incorrectly</exception>
        /// <exception cref="ArgumentException">
        /// The count of 'definitions' is 0.
        /// or
        /// At least one of definitions is null.
        /// </exception>
        public async Task<HttpResponseMessage> GetMultipleTimeSeriesAsync(
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions,
            byte serializationVersion = MetricQueryResponseDeserializer.CurrentVersion,
            bool returnMetricNames = false,
            Guid? traceId = null)
        {
            if (definitions == null)
            {
                throw new ArgumentNullException(nameof(definitions));
            }

            var definitionArray = definitions.ToArray();

            if (definitionArray.Length == 0)
            {
                throw new ArgumentException("The count of 'definitions' is 0.");
            }

            if (definitionArray.Any(d => d == null))
            {
                throw new ArgumentException("At least one of definitions is null.");
            }

            string operation = $"{this.DataRelativeUrl}/binary/version/{serializationVersion}";
            string monitoringAccount = definitionArray[0].Id.MonitoringAccount;

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/returnMetricNames/{3}",
                this.connectionInfo.GetMetricsDataQueryEndpoint(monitoringAccount).OriginalString,
                operation,
                monitoringAccount,
                returnMetricNames);

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                monitoringAccount,
                operation,
                definitionArray,
                this.clientId,
                null,
                traceId,
                serializationVersion).ConfigureAwait(false);

            return response.Item2;
        }

        /// <summary>
        /// Gets time series filtered by dimension criteral and a Top N series condition.
        /// </summary>
        /// <param name="filteredQueryRequests">The filtered query requests.</param>
        /// <param name="serializationVersion">The serialization version.</param>
        /// <param name="maximumAllowedQueryCost">The maximum allowed query cost.</param>
        /// <param name="traceId">The trace identifier.</param>
        /// <param name="returnRequestObjectOnFailure">if set to <c>true</c> [return request object on failure].</param>
        /// <returns>
        /// The raw stream of query results.
        /// </returns>
        [Obsolete]
        public async Task<HttpResponseMessage> GetFilteredTimeSeriesAsync(
            IReadOnlyList<FilteredTimeSeriesQueryRequest> filteredQueryRequests,
            byte serializationVersion,
            long maximumAllowedQueryCost,
            Guid traceId,
            bool returnRequestObjectOnFailure)
        {
            if (filteredQueryRequests == null)
            {
                throw new ArgumentNullException(nameof(filteredQueryRequests));
            }

            if (filteredQueryRequests.Count == 0)
            {
                throw new ArgumentException("The count of 'filteredQueryRequests' is 0.");
            }

            if (filteredQueryRequests.Any(d => d == null))
            {
                throw new ArgumentException("At least one of filteredQueryRequests is null.");
            }

            var url = string.Format(
                "{0}{1}/v1/multiple/serializationVersion/{2}/maxCost/{3}?timeoutInSeconds={4}&returnRequestObjectOnFailure={5}",
                this.connectionInfo.GetMetricsDataQueryEndpoint(filteredQueryRequests[0].MetricIdentifier.MonitoringAccount).OriginalString,
                this.QueryServiceRelativeUrl,
                serializationVersion,
                maximumAllowedQueryCost,
                (int)this.connectionInfo.Timeout.TotalSeconds,
                returnRequestObjectOnFailure);

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                null, // TODO add support of monitoring account on server side and then pass it here
                null, // TODO add support of monitoring account on server side and pass operation here
                filteredQueryRequests,
                this.clientId,
                null,
                traceId,
                serializationVersion).ConfigureAwait(false);

            return response.Item2;
        }

        /// <summary>
        /// Normalizes the time range.
        /// </summary>
        /// <param name="startTimeUtc">The start time in UTC.</param>
        /// <param name="endTimeUtc">The end time in UTC.</param>
        private static void NormalizeTimeRange(ref DateTime startTimeUtc, ref DateTime endTimeUtc)
        {
            startTimeUtc = new DateTime(startTimeUtc.Ticks / TimeSpan.TicksPerMinute * TimeSpan.TicksPerMinute);
            endTimeUtc = new DateTime(endTimeUtc.Ticks / TimeSpan.TicksPerMinute * TimeSpan.TicksPerMinute);
        }

        /// <summary>
        /// Get dimensions names and constraints from dimension filters.
        /// </summary>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <returns>Dimension constraint</returns>
        /// <exception cref="MetricsClientException">Only one filter can be specified for a dimension.</exception>
        private static SortedDictionary<string, Tuple<bool, IReadOnlyList<string>>> GetDimensionNamesAndConstraints(IEnumerable<DimensionFilter> dimensionFilters)
        {
            var dimensionNamesAndConstraints =
                new SortedDictionary<string, Tuple<bool, IReadOnlyList<string>>>(StringComparer.OrdinalIgnoreCase);

            if (dimensionFilters != null)
            {
                foreach (var filter in dimensionFilters)
                {
                    Tuple<bool, IReadOnlyList<string>> tuple;
                    if (filter.DimensionValues == null)
                    {
                        tuple = Tuple.Create(true, (IReadOnlyList<string>)EmptyStringArray);
                    }
                    else
                    {
                        tuple = Tuple.Create(filter.IsExcludeFilter, filter.DimensionValues);
                    }

                    if (dimensionNamesAndConstraints.ContainsKey(filter.DimensionName))
                    {
                        throw new MetricsClientException(
                            "Only one filter can be specified for a dimension. Another filter already exists for dimension: " +
                            filter.DimensionName);
                    }

                    dimensionNamesAndConstraints.Add(filter.DimensionName, tuple);
                }
            }

            return dimensionNamesAndConstraints;
        }

        /// <summary>
        /// Builds the dimension names and constraints for DQ query.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingType">Sampling type to use for this metric.</param>
        /// <param name="reducer">The reducing function to apply to the time series.</param>
        /// <param name="queryFilter">Filter criteria to enforce on the query.</param>
        /// <param name="includeSeries">Indicate whether or not to include the raw time series data in the result.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.</param>
        /// <param name="dimensionNamesAndConstraints">TODO: Comment</param>
        /// <returns>Time series definitions matching the query criteria.</returns>
        private NameValueCollection BuildQueryParameters(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            Reducer reducer,
            QueryFilter queryFilter,
            bool includeSeries,
            SelectionClause selectionClause,
            AggregationType aggregationType,
            long seriesResolutionInMinutes,
            out SortedDictionary<string, Tuple<bool, IReadOnlyList<string>>> dimensionNamesAndConstraints)
        {
            // There is an internal collection called HttpValueCollection for which the ToString() value
            // correctly generates a query string.  We can access this collection type by calling parse
            // on an empty query string.
            var query = HttpUtility.ParseQueryString(string.Empty);
            var startTimeMillis = UnixEpochHelper.GetMillis(startTimeUtc);
            var endTimeMillis = UnixEpochHelper.GetMillis(endTimeUtc);

            if (startTimeMillis > endTimeMillis)
            {
                throw new ArgumentException("Start time must be before end time.");
            }

            if (queryFilter == null)
            {
                throw new ArgumentNullException("queryFilter");
            }

            if (reducer == Reducer.Undefined)
            {
                throw new ArgumentException("Reducer cannot not be undefined.  Use QueryFilter.NoFilter to get all time series.");
            }

            if (!object.ReferenceEquals(queryFilter, QueryFilter.NoFilter))
            {
                if (queryFilter.Operator == Operator.Undefined)
                {
                    throw new ArgumentException("Operator cannot not be undefined.  Use QueryFilter.NoFilter to get all time series.");
                }
            }

            query["SamplingType"] = samplingType.ToString();
            query["startTime"] = startTimeMillis.ToString();
            query["endTime"] = endTimeMillis.ToString();
            query["includeSeries"] = includeSeries.ToString();
            query["reducer"] = reducer.ToString();

            if (includeSeries)
            {
                query["seriesAggregationType"] = aggregationType.ToString();
                query["seriesResolution"] = (seriesResolutionInMinutes * MillisecondsPerMinute).ToString();
            }

            if (object.ReferenceEquals(queryFilter, QueryFilter.NoFilter))
            {
                query["noFilter"] = "true";
            }
            else
            {
                query["operator"] = queryFilter.Operator.ToString();
                query["operand"] = queryFilter.Operand.ToString(CultureInfo.InvariantCulture);
            }

            if (selectionClause != null && !object.ReferenceEquals(selectionClause, SelectionClause.AllResults))
            {
                query["selectionType"] = selectionClause.SelectionType.ToString();
                query["top"] = selectionClause.QuantityToSelect.ToString();
                query["orderBy"] = selectionClause.OrderBy.ToString();
            }

            dimensionNamesAndConstraints = GetDimensionNamesAndConstraints(dimensionFilters);
            return query;
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IReadOnlyHyperLogLogSketches.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System.Collections.Generic;

    /// <summary>
    /// Read-only interface for objects representing list of hyperloglog sketches.
    /// </summary>
    public interface IReadOnlyHyperLogLogSketches
    {
        /// <summary>
        /// Gets the total sketches count.
        /// </summary>
        uint HyperLogLogSketchesCount { get; }

        /// <summary>
        /// Gets the list of sketches: ordered pairs of distinct count dimension name and HyperLogLogSketch.
        /// </summary>
        IEnumerable<KeyValuePair<string, HyperLogLogSketch>> HyperLogLogSketches
        {
            get;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IQueryResultV3.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System.Collections.Generic;

    using Microsoft.Cloud.Metrics.Client.Metrics;

    /// <summary>
    /// Represents a single time series result, with one or more sampling types.
    /// </summary>
    public interface IQueryResultV3
    {
        /// <summary>
        /// Set of valid dimension name-value pairs that meet the query condition.
        /// </summary>
        IReadOnlyList<KeyValuePair<string, string>> DimensionList { get; }

        /// <summary>
        /// Gets the evaluated value for this time series that meets the condition set in the query (provided for evidence and/or sorting).
        /// </summary>
        double EvaluatedResult { get; }

        /// <summary>
        /// Gets the time series values for the requested sampling type.
        /// </summary>
        /// <param name="samplingType">The sampling type requested.</param>
        /// <returns>The array of datapoints for the requested sampling type/</returns>
        /// <remarks>
        /// double.NaN is the sentinel used to indicate there is no metric value.
        /// </remarks>
        double[] GetTimeSeriesValues(SamplingType samplingType);
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="RollupConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// Determine if rollup is enabled for this preaggregate.
    /// </summary>
    public sealed class RollupConfiguration : IRollupConfiguration
    {
        /// <summary>
        /// RollupConfiguration where rollup is enabled.
        /// </summary>
        public static readonly RollupConfiguration RollupEnabled = new RollupConfiguration(true);

        /// <summary>
        /// RollupConfiguration where rollup is disabled.
        /// </summary>
        public static readonly RollupConfiguration RollupDisabled = new RollupConfiguration(false);

        /// <summary>
        /// Initializes a new instance of the <see cref="RollupConfiguration"/> class.
        /// </summary>
        /// <param name="enabled">Whether or not the feature is enabled.</param>
        [JsonConstructor]
        internal RollupConfiguration(bool enabled)
        {
            this.Enabled = enabled;
        }

        /// <summary>
        /// Determines if the rollup is enabled or disabled.
        /// </summary>
        public bool Enabled { get; }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IMetricConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;

    /// <summary>
    /// Interface representing an MDM Metric.
    /// </summary>
    public interface IMetricConfiguration
    {
        /// <summary>
        /// The namespace of the metric.
        /// </summary>
        string MetricNamespace { get; }

        /// <summary>
        /// The name of the metric.
        /// </summary>
        string Name { get; }

        /// <summary>
        /// The last updated time of the metric.
        /// </summary>
        DateTime LastUpdatedTime { get; }

        /// <summary>
        /// The last entity to update the metric.
        /// </summary>
        string LastUpdatedBy { get; }

        /// <summary>
        /// The version of the metric.
        /// </summary>
        uint Version { get; }

        /// <summary>
        /// Gets the description of the metric.
        /// </summary>
        string Description { get; }
    }
}
<file_sep>﻿// ------------------------------------------------------------------------------------------
// <copyright file="ClockType.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    // ReSharper disable UnusedMember.Global

    /// <summary>
    /// Types of clock that can be selected for the ETW session.
    /// </summary>
    /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364160(v=vs.85).aspx"/>
    internal enum ClockType
    {
        /// <summary>
        /// The default clock type to be used by the session, it is equivalent of selecting PerformanceCounter value.
        /// </summary>
        Default = 0,

        /// <summary>
        /// Indicates that the session uses the OS performance counter, a.k.a.: QPC. The resolution is typically 1000 times
        /// less than the CPU frequency of the box. It is the recommended way to collect high-resolution timestamps in Windows.
        /// </summary>
        Perf = 1,

        /// <summary>
        /// Indicates that the session uses the SystemTime clock (with actual resolution of ~15 milliseconds it is actually
        /// the cheaper timestamp available to ETW, the downside is the lack of resolution).
        /// </summary>
        System = 2,

        /// <summary>
        /// Indicates that the session uses the CPU timestamp (RDTSC instruction to retrieve the TSC). It is the cheapest of
        /// all with the higher resolution but not guaranteed to be in sync between different processors in the box.
        /// </summary>
        Cycle = 3,
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SelectionClause.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    /// <summary>
    /// Ordering enumeration for Top N selection clause, default is descending.
    /// </summary>
    public enum OrderBy
    {
        /// <summary>
        /// Ordering not specified.  Selection clause is not considered valid.
        /// </summary>
        Undefined,

        /// <summary>
        /// Descending ordering for Top N selection clause, this is the default value.
        /// </summary>
        Descending,

        /// <summary>
        /// Ascending ordering for Top N selection clause.
        /// </summary>
        Ascending,
    }

    /// <summary>
    /// The type of selection to perform.
    /// </summary>
    public enum SelectionType
    {
        /// <summary>
        /// Selection type not specified.  Selection clause is not considered valid.
        /// </summary>
        Undefined,

        /// <summary>
        /// Top N should return the top N values that meet the filter criteria
        /// </summary>
        TopValues,

        /// <summary>
        /// Top N should return the top N percent of values that meet the filter criteria
        /// </summary>
        TopPercent,
    }

    /// <summary>
    /// This class represents the selection clause of the query
    /// </summary>
    public sealed class SelectionClause
    {
        /// <summary>
        /// Selection clause to indicate that all results should be returned.
        /// </summary>
        public static readonly SelectionClause AllResults = new SelectionClause(SelectionType.Undefined, 0, OrderBy.Undefined);

        /// <summary>
        /// Initializes a new instance of the <see cref="SelectionClause"/> class.
        /// </summary>
        /// <param name="selectionType">Type of the selection clause.</param>
        /// <param name="quantityToSelect">The quantity to select.</param>
        /// <param name="orderBy">The ordering of the selection.</param>
        public SelectionClause(SelectionType selectionType, int quantityToSelect, OrderBy orderBy)
        {
            this.SelectionType = selectionType;
            this.QuantityToSelect = quantityToSelect;
            this.OrderBy = orderBy;
        }

        /// <summary>
        /// Gets the type of the selection clause.
        /// </summary>
        public SelectionType SelectionType { get; private set; }

        /// <summary>
        /// Gets the quantity to select.
        /// </summary>
        public int QuantityToSelect { get; private set; }

        /// <summary>
        /// Gets the ordering of the selection
        /// </summary>
        public OrderBy OrderBy { get; private set; }

        /// <summary>
        /// Determines whether the specified <see cref="object" />, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as SelectionClause);
        }

        /// <summary>
        /// Get hash code for this object.
        /// </summary>
        /// <returns>The hash code.</returns>
        public override int GetHashCode()
        {
            return this.SelectionType.GetHashCode() ^ this.QuantityToSelect.GetHashCode() ^ this.OrderBy.GetHashCode();
        }

        /// <summary>
        /// Compare this Selection Clause to the specified other clause.
        /// </summary>
        /// <param name="otherClause">The other clause.</param>
        /// <returns>Result of the equality test.</returns>
        private bool Equals(SelectionClause otherClause)
        {
            if (otherClause == null)
            {
                return false;
            }

            return this.SelectionType.Equals(otherClause.SelectionType)
                   && this.QuantityToSelect.Equals(otherClause.QuantityToSelect)
                   && this.OrderBy == otherClause.OrderBy;
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="RawMetricConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Metrics;
    using Newtonsoft.Json;
    using Online.Metrics.Serialization;

    /// <summary>
    /// Represents a raw metric in MDM.
    /// </summary>
    public sealed class RawMetricConfiguration : IRawMetricConfiguration
    {
        private readonly List<IPreaggregation> preaggregations;
        private readonly List<IComputedSamplingTypeExpression> computedSamplingTypes;
        private string description;

        /// <summary>
        /// Initializes a new instance of the <see cref="RawMetricConfiguration"/> class.
        /// </summary>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="name">The name.</param>
        /// <param name="lastUpdatedTime">The last updated time.</param>
        /// <param name="lastUpdatedBy">The last updated by.</param>
        /// <param name="version">The version.</param>
        /// <param name="scalingFactor">The scaling factor.</param>
        /// <param name="enableClientPublication">if set to <c>true</c> to enable client publication.</param>
        /// <param name="enableClientForking">if set to <c>true</c> to enable client forking.</param>
        /// <param name="description">The description of the metric.</param>
        /// <param name="dimensions">The dimensions.</param>
        /// <param name="preaggregations">The preaggregations.</param>
        /// <param name="rawSamplingTypes">The raw sampling types.</param>
        /// <param name="computedSamplingTypes">The computed sampling types.</param>
        /// <param name="useClientSideLastSamplingMode">Whether or not to only use the last value written within the sample period.</param>
        /// <param name="useClientSideEtwPublication">Whether or not the metric should be published to the aggregated ETW provider.</param>
        [JsonConstructor]
        internal RawMetricConfiguration(
            string metricNamespace,
            string name,
            DateTime lastUpdatedTime,
            string lastUpdatedBy,
            uint version,
            float? scalingFactor,
            bool enableClientPublication,
            bool enableClientForking,
            string description,
            IEnumerable<string> dimensions,
            IEnumerable<IPreaggregation> preaggregations,
            IEnumerable<SamplingType> rawSamplingTypes,
            IEnumerable<IComputedSamplingTypeExpression> computedSamplingTypes,
            bool useClientSideLastSamplingMode,
            bool useClientSideEtwPublication)
        {
            this.MetricNamespace = metricNamespace;
            this.Name = name;
            this.LastUpdatedTime = lastUpdatedTime;
            this.LastUpdatedBy = lastUpdatedBy;
            this.Version = version;
            this.ScalingFactor = scalingFactor;
            this.EnableClientPublication = enableClientPublication;
            this.EnableClientForking = enableClientForking;
            this.Description = description;
            this.Dimensions = dimensions;
            this.preaggregations = preaggregations.ToList();
            this.RawSamplingTypes = rawSamplingTypes;
            this.computedSamplingTypes = computedSamplingTypes.ToList();
            this.EnableClientSideLastSamplingMode = useClientSideLastSamplingMode;
            this.EnableClientEtwPublication = useClientSideEtwPublication;
        }

        /// <summary>
        /// The namespace of the metric.
        /// </summary>
        public string MetricNamespace { get; }

        /// <summary>
        /// The name of the metric.
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// The last updated time of the metric.
        /// </summary>
        public DateTime LastUpdatedTime { get; }

        /// <summary>
        /// The last entity to update the metric.
        /// </summary>
        public string LastUpdatedBy { get; }

        /// <summary>
        /// The version of the metric.
        /// </summary>
        public uint Version { get; }

        /// <summary>
        /// Gets or sets the scaling factor.
        /// </summary>
        public float? ScalingFactor { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether client publication is enabled.
        /// </summary>
        public bool EnableClientPublication { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether client forking is enabled.
        /// </summary>
        public bool EnableClientForking { get; set; }

        /// <summary>
        /// Gets the description of the metric.
        /// </summary>
        public string Description
        {
            get
            {
                return this.description;
            }

            set
            {
                if (value != null && value.Length > SerializationConstants.MaximumMetricDescriptionLength)
                {
                    throw new ArgumentOutOfRangeException(
                        nameof(value),
                        $"The metric description cannot be greater than {SerializationConstants.MaximumMetricDescriptionLength} characters.");
                }

                this.description = value;
            }
        }

        /// <summary>
        /// Gets the raw sampling types (Sum, Count, or legacy MetricsClient sampling types).
        /// </summary>
        public IEnumerable<SamplingType> RawSamplingTypes { get; }

        /// <summary>
        /// Gets the preaggregations of the metric.
        /// </summary>
        public IEnumerable<IPreaggregation> Preaggregations
        {
            get { return this.preaggregations; }
        }

        /// <summary>
        /// Gets the dimensions of the metric.
        /// </summary>
        public IEnumerable<string> Dimensions { get; }

        /// <summary>
        /// Gets the computed sampling types.
        /// </summary>
        public IEnumerable<IComputedSamplingTypeExpression> ComputedSamplingTypes
        {
            get { return this.computedSamplingTypes; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether only the last value seen for a time series is perserved on the client.
        /// </summary>
        /// <value>
        /// <c>true</c> if last sampling mode is used; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        /// Client side last sampling mode means that within the collection interval (1m) only the last value set to the metric is kept.  This means Sum == Min == Max
        /// and Count == 1 for this metric when it is sent to the server.
        /// </remarks>
        [JsonProperty]
        public bool EnableClientSideLastSamplingMode { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the metric will be published to the aggregated ETW channel.
        /// </summary>
        public bool EnableClientEtwPublication { get; set; }

        /// <summary>
        /// Determines whether this instance can add preaggregation to the metric configuration.
        /// </summary>
        /// <param name="preaggregationToAdd">The preaggregation to add.</param>
        /// <returns>
        /// True if the preaggregation can be added.
        /// </returns>
        public bool CanAddPreaggregation(IPreaggregation preaggregationToAdd)
        {
            if (preaggregationToAdd == null)
            {
                throw new ArgumentNullException(nameof(preaggregationToAdd));
            }

            foreach (var preaggregation in this.preaggregations)
            {
                if (string.Equals(preaggregationToAdd.Name, preaggregation.Name, StringComparison.OrdinalIgnoreCase))
                {
                    return false;
                }

                if (preaggregation.Dimensions.SequenceEqual(preaggregationToAdd.Dimensions, StringComparer.OrdinalIgnoreCase))
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Adds the preaggregate.
        /// </summary>
        /// <param name="preaggregate">The preaggregate.</param>
        public void AddPreaggregation(IPreaggregation preaggregate)
        {
            if (!this.CanAddPreaggregation(preaggregate))
            {
                throw new ConfigurationValidationException("Duplicate preaggregates cannot be added.", ValidationType.DuplicatePreaggregate);
            }

            this.preaggregations.Add(preaggregate);
        }

        /// <summary>
        /// Removes the preaggregate.
        /// </summary>
        /// <param name="preaggregateName">The name of the preaggregate to remove.</param>
        public void RemovePreaggregation(string preaggregateName)
        {
            if (string.IsNullOrWhiteSpace(preaggregateName))
            {
                throw new ArgumentNullException(nameof(preaggregateName));
            }

            this.preaggregations.RemoveAll(x => string.Equals(x.Name, preaggregateName, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// Adds the type of the computed sampling.
        /// </summary>
        /// <param name="computedSamplingType">Type of the computed sampling.</param>
        public void AddComputedSamplingType(IComputedSamplingTypeExpression computedSamplingType)
        {
            if (this.computedSamplingTypes.Any(x => x.Name.Equals(computedSamplingType.Name, StringComparison.OrdinalIgnoreCase)))
            {
                throw new ConfigurationValidationException("Duplicate computed sampling types cannot be added.", ValidationType.DuplicateSamplingType);
            }

            this.computedSamplingTypes.Add(computedSamplingType);
        }

        /// <summary>
        /// Removes the type of the computed sampling.
        /// </summary>
        /// <param name="computedSamplingTypeName">Name of the computed sampling type.</param>
        public void RemoveComputedSamplingType(string computedSamplingTypeName)
        {
            if (string.IsNullOrWhiteSpace(computedSamplingTypeName))
            {
                throw new ArgumentNullException(nameof(computedSamplingTypeName));
            }

            for (var i = 0; i < this.computedSamplingTypes.Count; ++i)
            {
                if (string.Equals(computedSamplingTypeName, this.computedSamplingTypes[i].Name, StringComparison.OrdinalIgnoreCase))
                {
                    if (this.computedSamplingTypes[i].IsBuiltIn)
                    {
                        throw new ConfigurationValidationException("Built in computed sampling types cannot be removed.", ValidationType.BuiltInTypeRemoved);
                    }

                    this.computedSamplingTypes.RemoveAt(i);

                    return;
                }
            }
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="MetricDeserializer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;

    /// <summary>
    /// Class which deserializes Autopilot count (metric) data.
    /// Serialization format corresponds to one described in http://sharepoint/sites/autopilot/team/Docs/Silicon/Monitroing%20Team/AD%20Metrics%20%20Autopilot%20Counter%20Data%20Common%20Serialization%20Format.docx.
    /// </summary>
    /// <typeparam name="TMetadata">Type of metadata object used for deserialization.</typeparam>
    public sealed class MetricDeserializer<TMetadata>
        where TMetadata : IMetricMetadata
    {
        private const ushort MaxVersion = 5;
        private const uint TypeSerializerFlags = 0x12020000; // Corresponds to 0001.001.0000.0001.00000000000000000 (Use string and metadata interning with variable-length integer serialization)
        private readonly List<string> stringsDictionary = new List<string>();
        private readonly List<TMetadata> metadataDictionary = new List<TMetadata>();
        private readonly List<KeyValuePair<ulong, uint>> histogramBuffer = new List<KeyValuePair<ulong, uint>>();

        /// <summary>
        /// Validates the data packet by CRC check
        /// </summary>
        /// <param name="dataPacket">Data packet to check.</param>
        /// <exception cref="CrcCheckFailedSerializationException">
        /// Throws when CRC check fails.
        /// </exception>
        public static void ValidateCrc(byte[] dataPacket)
        {
            var version = (ushort)(dataPacket[0] | dataPacket[1] << 8);

            if (version < 5)
            {
                // No CRC is added for versions less than 5.
                return;
            }

            var crc = (uint)(dataPacket[2] | dataPacket[3] << 8 | dataPacket[4] << 16 | dataPacket[5] << 24);
            var computedCrc = Crc.ComputeCrc(0, dataPacket, 6);

            if (crc != computedCrc)
            {
                throw new CrcCheckFailedSerializationException($"Crc check failed. Computed CRC : {crc}, Packet CRC: {computedCrc}");
            }
        }

        /// <summary>
        /// Clears the deserializer state.
        /// </summary>
        public void Clear()
        {
            this.stringsDictionary.Clear();
            this.metadataDictionary.Clear();
            this.histogramBuffer.Clear();
        }

        /// <summary>
        /// Deserializes counter (metric) data from the stream and adds all objects to provided collection.
        /// </summary>
        /// <param name="stream">Stream from which data should be deserialized. Stream should be readable and provide randon access.</param>
        /// <param name="metricBuilder">An object responsible for creation and further consumption of deserialized data.</param>
        /// <param name="maxMetricStringsLength">Maximum length of strings, which represent metric name, dimension names and values.</param>
        /// <param name="maxMetricNamespaceStringsLength">Maximum length of metric namespace string.</param>
        /// <param name="maxMetricDimensionValueStringsLength">Maximum length of metric dimension value string.</param>
        public void Deserialize(Stream stream, IMetricBuilder<TMetadata> metricBuilder, int maxMetricStringsLength, int maxMetricNamespaceStringsLength, int maxMetricDimensionValueStringsLength)
        {
            if (!stream.CanRead || !stream.CanSeek)
            {
                throw new ArgumentException(@"Stream should be readable and provide random access.", nameof(stream));
            }

            try
            {
                using (var reader = new NoCloseBinaryReader(stream, Encoding.UTF8))
                {
                    var startStreamPosition = stream.Position;

                    // Read version and type serializers info
                    var version = reader.ReadUInt16();
                    if (version > MaxVersion)
                    {
                        throw new VersionNotSupportedMetricSerializationException(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "Version is not supported. Read version:{0}, Max version:{1}.",
                                version,
                                MaxVersion));
                    }

                    if (version >= 5)
                    {
                        // Read CRC. CRC check is done in upper layers.
                        reader.ReadUInt32();
                    }

                    if (reader.ReadUInt32() != TypeSerializerFlags)
                    {
                        throw new VersionNotSupportedMetricSerializationException("Type serializers not supported.");
                    }

                    metricBuilder.SetSerializationVersion(version);

                    // Read strings
                    var deserializerDataPosition = stream.Position;
                    stream.Position += sizeof(long);
                    stream.Position = startStreamPosition + reader.ReadInt64();
                    var count = SerializationUtils.ReadUInt32FromBase128(reader);
                    for (uint i = 0; i < count; ++i)
                    {
                        this.stringsDictionary.Add(reader.ReadString());
                    }

                    // Read metrics metadata
                    stream.Position = deserializerDataPosition;
                    stream.Position = startStreamPosition + reader.ReadInt64();
                    count = SerializationUtils.ReadUInt32FromBase128(reader);
                    for (uint i = 0; i < count; ++i)
                    {
                        this.metadataDictionary.Add(this.ReadMetricMetadata(reader, metricBuilder, version, maxMetricStringsLength, maxMetricNamespaceStringsLength));
                    }

                    // Read metrics data
                    stream.Position = deserializerDataPosition + (2 * sizeof(long));
                    this.ReadMetricsData(reader, metricBuilder, version, maxMetricStringsLength, maxMetricNamespaceStringsLength, maxMetricDimensionValueStringsLength);
                }
            }
            catch (IOException ioException)
            {
                throw new MetricSerializationException("Failed to deserialize data. Problem with input stream.", ioException);
            }
            catch (Exception exception)
            {
                throw new MetricSerializationException("Failed to deserialize data. Likely the incoming stream contains corrupted data.", exception);
            }
            finally
            {
                this.metadataDictionary.Clear();
                this.stringsDictionary.Clear();
                this.histogramBuffer.Clear();
            }
        }

        private List<KeyValuePair<ulong, uint>> ReadHistogram(BinaryReader reader, ushort version)
        {
            SerializationUtils.ReadHistogramTo(this.histogramBuffer, reader, version > 3);

            return this.histogramBuffer;
        }

        private void ReadMetricsData(BinaryReader reader, IMetricBuilder<TMetadata> metricBuilder, ushort version, int maxMetricStringsLength, int maxMetricNamespaceStringsLength, int maxMetricDimensionValueStringsLength)
        {
            long packetTime = 0;

            if (version >= 5)
            {
                packetTime = (long)SerializationUtils.ReadUInt64FromBase128(reader);
            }

            // Versions before 2 used variable number of bytes to write number of serialized metrics data.
            // From version 2 passing IEnumerable<IReadOnlyMetric> is supported, thus number of metrics data
            // is unknown beforehand and we cannot use variable number anymore. Thus fixed 4 bytes uint is used.
            var count = version >= 2 ? reader.ReadUInt32() : SerializationUtils.ReadUInt32FromBase128(reader);
            for (var i = 0; i < count; ++i)
            {
                metricBuilder.BeginMetricCreation();
                var metadata = this.ReadMetricMetadataByIndex(reader);
                metricBuilder.AssignMetadata(metadata);

                // In versions 0-2 Monitoring Account and Metric Namespace was part of the Metric data
                // From version 3 Monitoring Account is removed and Metric Namespace became a part of Metric Metadata
                if (version < 3)
                {
                    var monitoringAccount = this.ReadStringByIndex(reader);
                    if (monitoringAccount.Length > maxMetricStringsLength)
                    {
                        throw new MetricSerializationException($"Monitoring account string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricStringsLength}, Value:{monitoringAccount}.", null);
                    }

                    var metricNamespace = this.ReadStringByIndex(reader);
                    if (metricNamespace.Length > maxMetricNamespaceStringsLength)
                    {
                        throw new MetricSerializationException($"Namespace string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricNamespaceStringsLength}, Value:{metricNamespace}.", null);
                    }

                    metricBuilder.AssignMonitoringAccount(monitoringAccount);
                    metricBuilder.AssignNamespace(metricNamespace);
                }

                if (version == 0)
                {
                    // Skip event id
                    this.ReadStringByIndex(reader);
                }

                if (version >= 5)
                {
                    var timeInTicks = (packetTime - SerializationUtils.ReadInt64FromBase128(reader)) * SerializationUtils.OneMinuteInterval;
                    metricBuilder.AssignTimeUtc(new DateTime(timeInTicks, DateTimeKind.Utc));
                }
                else
                {
                    metricBuilder.AssignTimeUtc(new DateTime((long)SerializationUtils.ReadUInt64FromBase128(reader), DateTimeKind.Utc));
                }

                for (var j = 0; j < metadata.DimensionsCount; ++j)
                {
                    var dimensionValue = this.ReadStringByIndex(reader);
                    if (dimensionValue.Length > maxMetricDimensionValueStringsLength)
                    {
                        throw new MetricSerializationException($"Dimension value string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricDimensionValueStringsLength}, Value:{dimensionValue}.", null);
                    }

                    metricBuilder.AddDimensionValue(dimensionValue);
                }

                var samplingTypes = (SamplingTypes)SerializationUtils.ReadUInt32FromBase128(reader);
                metricBuilder.AssignSamplingTypes(samplingTypes);

                if ((samplingTypes & SamplingTypes.Min) != 0)
                {
                    metricBuilder.AssignMin(SerializationUtils.ReadUInt64FromBase128(reader));
                }

                if ((samplingTypes & SamplingTypes.Max) != 0)
                {
                    metricBuilder.AssignMax(SerializationUtils.ReadUInt64FromBase128(reader));
                }

                if ((samplingTypes & SamplingTypes.Sum) != 0)
                {
                    metricBuilder.AssignSum(SerializationUtils.ReadUInt64FromBase128(reader));
                }

                if ((samplingTypes & SamplingTypes.Count) != 0)
                {
                    metricBuilder.AssignCount(SerializationUtils.ReadUInt32FromBase128(reader));
                }

                if ((samplingTypes & SamplingTypes.SumOfSquareDiffFromMean) != 0)
                {
                    var sumOfSquareDiffFromMean = reader.ReadDouble();
                    metricBuilder.AssignSumOfSquareDiffFromMean(sumOfSquareDiffFromMean);
                }

                if ((samplingTypes & SamplingTypes.Histogram) != 0)
                {
                    metricBuilder.AssignHistogram(this.ReadHistogram(reader, version));
                }

                if ((samplingTypes & SamplingTypes.HyperLogLogSketch) != 0)
                {
                    var sizeOfHyperLogLogSketches = reader.ReadInt32();
                    metricBuilder.AssignHyperLogLogSketch(reader, sizeOfHyperLogLogSketches);
                }

                metricBuilder.EndMetricCreation();
            }
        }

        private string ReadStringByIndex(BinaryReader reader)
        {
            var index = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            return this.stringsDictionary[index];
        }

        private TMetadata ReadMetricMetadataByIndex(BinaryReader reader)
        {
            var index = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            return this.metadataDictionary[index];
        }

        private TMetadata ReadMetricMetadata(BinaryReader reader, IMetricBuilder<TMetadata> metricBuilder, ushort version, int maxMetricStringsLength, int maxMetricNamespaceStringsLength)
        {
            var metricNamespace = string.Empty;

            // In versions 0-2 Metric Namespace was part of the Metric data, from version 3 it became a part of Metric Metadata
            if (version >= 3)
            {
                metricNamespace = this.ReadStringByIndex(reader);
                if (metricNamespace.Length > maxMetricNamespaceStringsLength)
                {
                    throw new MetricSerializationException($"Namespace string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricNamespaceStringsLength}, Value:{metricNamespace}.", null);
                }
            }

            var metricName = this.ReadStringByIndex(reader);
            if (metricName.Length > maxMetricStringsLength)
            {
                throw new MetricSerializationException($"Metric name string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricStringsLength}, Value:{metricName}.", null);
            }

            var count = SerializationUtils.ReadUInt32FromBase128(reader);
            var dimensionNames = new List<string>((int)count);
            for (var i = 0; i < count; ++i)
            {
                var dimensionName = this.ReadStringByIndex(reader);
                if (dimensionName.Length > maxMetricStringsLength)
                {
                    throw new MetricSerializationException($"Dimension name string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricStringsLength}, Value:{dimensionName}.", null);
                }

                dimensionNames.Add(dimensionName);
            }

            return metricBuilder.CreateMetadata(metricNamespace, metricName, dimensionNames);
        }

        /// <summary>
        /// No close binary reader.
        /// </summary>
        public class NoCloseBinaryReader : BinaryReader
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="NoCloseBinaryReader"/> class.
            /// </summary>
            /// <param name="stream">The stream.</param>
            /// <param name="encoding">The encoding.</param>
            public NoCloseBinaryReader(Stream stream, Encoding encoding)
                : base(stream, encoding)
            {
            }

            /// <inheritdoc />
            protected override void Dispose(bool disposing)
            {
                this.BaseStream.Flush();
                base.Dispose(false);
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IReadOnlyMetric.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// <author email="selavrin">
//     <NAME>
// </author>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.IO;
    using global::Metrics.Services.Common.BlobSegment;

    /// <summary>
    /// Read only interface for metric data.
    /// </summary>
    /// TODO: Once all publishers and recievers are moved to version 3 of serialization remove MonitoringAccount and MetricNamespace from here
    public interface IReadOnlyMetric
    {
        /// <summary>
        /// Gets the Monitoring Account to which this metric is reported.
        /// </summary>
        string MonitoringAccount { get; }

        /// <summary>
        /// Gets the metric namespace.
        /// </summary>
        string MetricNamespace { get; }

        /// <summary>
        /// Gets the time when metric was reported.
        /// </summary>
        DateTime TimeUtc { get; }

        /// <summary>
        /// Gets the metric metadata.
        /// </summary>
        IMetricMetadata MetricMetadata { get; }

        /// <summary>
        /// Gets the sampling types this metric contains.
        /// </summary>
        SamplingTypes SamplingTypes { get; }

        /// <summary>
        /// Gets the number of samples for which this metric is reported.
        /// </summary>
        uint Count { get; }

        /// <summary>
        /// Gets the minimum value of samples reported this metric.
        /// </summary>
        ulong Min { get; }

        /// <summary>
        /// Gets the maximum value of samples reported this metric.
        /// </summary>
        ulong Max { get; }

        /// <summary>
        /// Gets the sum of sample values reported this metric.
        /// </summary>
        ulong Sum { get; }

        /// <summary>
        /// Gets the sum of squares differences from the mean for the sample values reported this metric.
        /// </summary>
        double SumOfSquareDiffFromMean { get; }

        /// <summary>
        /// Gets the minimum value of samples reported this metric.
        /// </summary>
        MetricValueV2 MinUnion { get; }

        /// <summary>
        /// Gets the maximum value of samples reported this metric.
        /// </summary>
        MetricValueV2 MaxUnion { get; }

        /// <summary>
        /// Gets the sum of sample values reported this metric.
        /// </summary>
        MetricValueV2 SumUnion { get; }

        /// <summary>
        /// Gets the histogram created from sample values reported for this metric.
        /// </summary>
        IReadOnlyHistogram Histogram { get; }

        /// <summary>
        /// Gets the tdigest created from sample values reported for this metric.
        /// </summary>
        IReadOnlyTDigest TDigest { get; }

        /// <summary>
        /// Gets the hyperloglog sketches from this metric.
        /// </summary>
        IReadOnlyHyperLogLogSketches HyperLogLogSketches { get; }

        /// <summary>
        /// Gets the hyperloglog sketches stream.
        /// </summary>
        /// <remarks>
        /// This will be null if HyperLogLogSketches is set and vice-versa.
        /// </remarks>
        Stream HyperLogLogSketchesStream { get; }

        /// <summary>
        /// Gets the value of the dimension by dimension index.
        /// Implementer of this interface has to make sure that number of dimensions specified in MetricMetadata be equal to number of dimension values.
        /// </summary>
        /// <param name="dimensionIndex">Index of the dimension for which to get value in 0..MetricMetadata.DimensionsCount range.</param>
        /// <returns>Value of the dimension.</returns>
        string GetDimensionValue(int dimensionIndex);
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="MetricValueV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;

    /// <summary>
    /// Represents metric value as union of Long, Ulong, Double.
    /// </summary>
    [StructLayout(LayoutKind.Explicit)]
    public struct MetricValueV2
    {
        /// <summary>
        /// The value as double datatype.
        /// </summary>
        [FieldOffset(0)]
        public double ValueAsDouble;

        /// <summary>
        /// The value as long datatype.
        /// </summary>
        [FieldOffset(0)]
        public long ValueAsLong;

        /// <summary>
        /// The value as unsigned long datatype.
        /// </summary>
        [FieldOffset(0)]
        public ulong ValueAsULong;

        public static explicit operator MetricValueV2(ulong v)
        {
            return new MetricValueV2 { ValueAsULong = v };
        }

        public static explicit operator MetricValueV2(long v)
        {
            return new MetricValueV2 { ValueAsLong = v };
        }

        public static explicit operator MetricValueV2(int v)
        {
            return new MetricValueV2 { ValueAsLong = v };
        }

        public static explicit operator MetricValueV2(uint v)
        {
            return new MetricValueV2 { ValueAsLong = v };
        }

        public static explicit operator MetricValueV2(double v)
        {
            return new MetricValueV2 { ValueAsDouble = v };
        }

        /// <summary>
        /// Determines whether this instance value for double can be represented as long
        /// </summary>
        /// <returns>True if the value can be represented as long.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool CanRepresentDoubleAsLong()
        {
            // ReSharper disable once CompareOfFloatsByEqualityOperator
            return this.ValueAsDouble - (long)this.ValueAsDouble == 0;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConnectionInfo.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Sockets;
    using System.Security.Cryptography.X509Certificates;
    using System.Threading;
    using System.Threading.Tasks;
    using Logging;
    using Newtonsoft.Json;
    using Utility;

    /// <summary>
    /// Enumeration to distinguish the MDM environments that will be used for the connections.
    /// </summary>
    public enum MdmEnvironment
    {
        /// <summary>
        /// Uses the MDM/Jarvis production environments.
        /// </summary>
        Production,

        /// <summary>
        /// Uses the MDM/Jarvis INT environments.
        /// </summary>
        Int
    }

    /// <summary>
    /// The connection information used to connect to metrics backend.
    /// </summary>
    public sealed class ConnectionInfo
    {
        /// <summary>
        /// Possible part of operation URI.
        /// </summary>
        public const string CertApiFirstSegment = "/api/";

        /// <summary>
        /// Possible part of operation URI.
        /// </summary>
        public const string UserApiFirstSegment = "/user-api/";

        /// <summary>
        /// The default timeout.
        /// </summary>
        internal static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(100);

        /// <summary>
        /// There just two types of global MDM environment: INT and PROD. It must match the values defined for <see cref="MdmEnvironment"/>.
        /// </summary>
        private const int NumberOfMdmGlobalEnvironments = 2;

        /// <summary>
        /// The minimum timeout.
        /// </summary>
        private static readonly TimeSpan MinTimeout = TimeSpan.FromSeconds(30);

        /// <summary>
        /// The maximum timeout.
        /// </summary>
        private static readonly TimeSpan MaxTimeout = TimeSpan.FromSeconds(300);

        /// <summary>
        /// The log identifier for the class.
        /// </summary>
        private static readonly object LogId = Logger.CreateCustomLogId("ConnectionInfo");

        /// <summary>
        /// Maps the accounts to the account stamp information.
        /// </summary>
        private static readonly ConcurrentDictionary<string, StampInformation>[] AccountToUriMaps = new ConcurrentDictionary<string, StampInformation>[NumberOfMdmGlobalEnvironments];

        /// <summary>
        /// Maps the GSLB to the respective Uri object according to the environment type being targeted. This avoids duplication of URIs objects
        /// if the same instance is used to connect to many different endpoints, for most usages it is expected to carry a single value.
        /// </summary>
        private static readonly ConcurrentDictionary<string, Uri>[] GslbToUris = new ConcurrentDictionary<string, Uri>[NumberOfMdmGlobalEnvironments];

        /// <summary>
        /// The host to IP URI map.
        /// </summary>
        private static readonly ConcurrentDictionary<Uri, Uri> HostToIpUriMap = new ConcurrentDictionary<Uri, Uri>();

        /// <summary>
        /// Locks to control initialiation of statics related to a global environment.
        /// </summary>
        private static readonly object GlobalEnvironmentInitializationLock = new object();

        /// <summary>
        /// The timer to refresh accounts' home stamps.
        /// </summary>
        private static readonly Timer TimerToRefreshHomeStamp;

        /// <summary>
        /// The timer to refresh IP address.
        /// </summary>
        private static readonly Timer TimerToRefreshIpAddress;

        /// <summary>
        /// An HTTP client without authentication.
        /// </summary>
        private static readonly HttpClient HttpClientWithoutAuthentication = HttpClientHelper.CreateHttpClient(DefaultTimeout);

        /// <summary>
        /// The paths to the known global environments, its order must match the enum <see cref="MdmEnvironment"/> used to identify the desired
        /// global environment.
        /// </summary>
        private static volatile string[] globalEnvironments;

        /// <summary>
        /// The MDM environment being used, only makes sense if no <see cref="Endpoint"/> was specified in the construction of the instance.
        /// </summary>
        private readonly int mdmEnvironmentMapIndex;

        /// <summary>
        /// The certificate used to authenticate with MDM.
        /// </summary>
        private X509Certificate2 certificate;

        /// <summary>
        /// The thumbprint of the certificate used to authenticate with MDM.
        /// </summary>
        private string certificateThumbprint;

        /// <summary>
        /// Flag to tell if a global endpoint is specified.
        /// </summary>
        private bool? isGlobalEndpoint;

        /// <summary>
        /// Initializes static members of the <see cref="ConnectionInfo"/> class.
        /// </summary>
        static ConnectionInfo()
        {
            // Prefer TLS 1.2
            ServicePointManager.SecurityProtocol &= ~SecurityProtocolType.Ssl3;
            ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

            for (int index = 0; index < NumberOfMdmGlobalEnvironments; index++)
            {
                GslbToUris[index] = new ConcurrentDictionary<string, Uri>(StringComparer.OrdinalIgnoreCase);
                AccountToUriMaps[index] = new ConcurrentDictionary<string, StampInformation>(StringComparer.OrdinalIgnoreCase);
            }

            TimerToRefreshIpAddress = new Timer(state => RefreshIpAddresses(), null, DnsResolutionUpdateFrequency, System.Threading.Timeout.InfiniteTimeSpan);
            TimerToRefreshHomeStamp = new Timer(state => RefreshAccountHomeStamp(), null, HomeStampAutomaticUpdateFrequency, System.Threading.Timeout.InfiniteTimeSpan);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="endpoint">The endpoint.</param>
        /// <param name="certificateThumbprint">The certificate thumbprint.</param>
        public ConnectionInfo(Uri endpoint, string certificateThumbprint)
            : this(endpoint, certificateThumbprint, StoreLocation.LocalMachine)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class.
        /// </summary>
        /// <param name="certificateThumbprint">The certificate thumbprint.</param>
        /// <param name="mdmEnvironment">The to be targeted by the instance.</param>
        public ConnectionInfo(string certificateThumbprint, MdmEnvironment mdmEnvironment = MdmEnvironment.Production)
            : this(null, certificateThumbprint, StoreLocation.LocalMachine, null, DefaultTimeout, mdmEnvironment)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="endpoint">The endpoint.</param>
        /// <param name="certificate">The certificate used to authenticate with MDM.</param>
        public ConnectionInfo(Uri endpoint, X509Certificate2 certificate)
            : this(endpoint, null, StoreLocation.LocalMachine, certificate, DefaultTimeout)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo" /> class.
        /// </summary>
        /// <param name="endpoint">The endpoint of metrics backend servers.</param>
        /// <param name="certificate">The certificate used to authenticate with MDM.</param>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        public ConnectionInfo(Uri endpoint, X509Certificate2 certificate, TimeSpan timeout)
            : this(endpoint, null, StoreLocation.LocalMachine, certificate, timeout)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="endpoint">The endpoint.</param>
        public ConnectionInfo(Uri endpoint)
            : this(endpoint, null, StoreLocation.LocalMachine, null, DefaultTimeout)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        /// <param name="endpoint">The endpoint.</param>
        public ConnectionInfo(TimeSpan timeout, Uri endpoint)
            : this(endpoint, null, StoreLocation.LocalMachine, null, timeout)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class.
        /// </summary>
        /// <param name="certificate">The certificate used to authenticate with MDM.</param>
        /// <param name="mdmEnvironment">The global environment to be targeted by the instance.</param>
        public ConnectionInfo(X509Certificate2 certificate, MdmEnvironment mdmEnvironment = MdmEnvironment.Production)
            : this(null, null, StoreLocation.LocalMachine, certificate, DefaultTimeout, mdmEnvironment)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class.
        /// </summary>
        /// <param name="mdmEnvironment">The global environment to be targeted by the instance.</param>
        public ConnectionInfo(MdmEnvironment mdmEnvironment = MdmEnvironment.Production)
            : this(null, null, StoreLocation.LocalMachine, null, DefaultTimeout, mdmEnvironment)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class.
        /// </summary>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        /// <param name="mdmEnvironment">The global environment to be targeted by the instance.</param>
        public ConnectionInfo(TimeSpan timeout, MdmEnvironment mdmEnvironment = MdmEnvironment.Production)
            : this(null, null, StoreLocation.LocalMachine, null, timeout, mdmEnvironment)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="endpoint">The endpoint of metrics backend servers.</param>
        /// <param name="certificateThumbprint">The thumbprint of the certificate used to publish metrics data to the MDM.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        public ConnectionInfo(Uri endpoint, string certificateThumbprint, StoreLocation certificateStoreLocation)
            : this(endpoint, certificateThumbprint, certificateStoreLocation, DefaultTimeout)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo"/> class.
        /// </summary>
        /// <param name="certificateThumbprint">The thumbprint of the certificate used to publish metrics data to the MDM.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        /// <param name="mdmEnvironment">The global environment to be targeted by the instance.</param>
        public ConnectionInfo(string certificateThumbprint, StoreLocation certificateStoreLocation, MdmEnvironment mdmEnvironment = MdmEnvironment.Production)
            : this(null, certificateThumbprint, certificateStoreLocation, null, DefaultTimeout, mdmEnvironment)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo" /> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="endpoint">The endpoint of metrics backend servers.</param>
        /// <param name="certificateThumbprint">The thumbprint of the certificate used to publish metrics data to the MDM.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        public ConnectionInfo(Uri endpoint, string certificateThumbprint, StoreLocation certificateStoreLocation, TimeSpan timeout)
            : this(endpoint, certificateThumbprint, certificateStoreLocation, null, timeout)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo" /> class.
        /// </summary>
        /// <param name="certificateThumbprint">The thumbprint of the certificate used to publish metrics data to the MDM.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        /// <param name="mdmEnvironment">The global environment to be targeted by the instance.</param>
        public ConnectionInfo(string certificateThumbprint, StoreLocation certificateStoreLocation, TimeSpan timeout, MdmEnvironment mdmEnvironment = MdmEnvironment.Production)
            : this(null, certificateThumbprint, certificateStoreLocation, null, timeout, mdmEnvironment)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo" /> class for the special case of explicitly fixing the endpoint to be used.
        /// </summary>
        /// <param name="endpoint">The endpoint of metrics backend servers.</param>
        /// <param name="certificateThumbprint">The thumbprint of the certificate used to publish metrics data to the MDM.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        /// <param name="certificate">The certificate used to authenticate with MDM.</param>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        /// <exception cref="ArgumentNullException">
        /// endpoint
        /// or
        /// certificateThumbprint
        /// </exception>
        private ConnectionInfo(Uri endpoint, string certificateThumbprint, StoreLocation certificateStoreLocation, X509Certificate2 certificate, TimeSpan timeout)
            : this(endpoint, certificateThumbprint, certificateStoreLocation, certificate, timeout, MdmEnvironment.Production)
        {
            if (endpoint == null)
            {
                throw new ArgumentNullException(nameof(endpoint));
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionInfo" /> class.
        /// </summary>
        /// <param name="endpoint">The endpoint of metrics backend servers.</param>
        /// <param name="certificateThumbprint">The thumbprint of the certificate used to publish metrics data to the MDM.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        /// <param name="certificate">The certificate used to authenticate with MDM.</param>
        /// <param name="timeout">The time to wait before the request times out. The timeout value can range from 30 seconds to 5 minutes.</param>
        /// <param name="mdmEnvironment">The global environment to be targeted by the instance.</param>
        /// <exception cref="ArgumentNullException">
        /// endpoint
        /// or
        /// certificateThumbprint
        /// </exception>
        private ConnectionInfo(
            Uri endpoint,
            string certificateThumbprint,
            StoreLocation certificateStoreLocation,
            X509Certificate2 certificate,
            TimeSpan timeout,
            MdmEnvironment mdmEnvironment)
        {
            if (certificate != null && !string.IsNullOrWhiteSpace(certificateThumbprint))
            {
                throw new ArgumentException($"Either {nameof(certificate)} or {nameof(certificateThumbprint)} can be specified, but not both.");
            }

            if (timeout < MinTimeout || timeout > MaxTimeout)
            {
                throw new ArgumentException($"The timeout value for a request must be between {MinTimeout} and {MaxTimeout}.");
            }

            if (certificate == null && string.IsNullOrWhiteSpace(certificateThumbprint))
            {
                this.UseAadUserAuthentication = true;
            }

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                "Created",
                "A new connection was created. Endpoint:{0}, CertThumbprint:{1}, CertStore:{2}, TimeoutMs:{3}",
                endpoint,
                certificate != null ? certificate.Thumbprint : certificateThumbprint,
                certificateStoreLocation,
                timeout.TotalMilliseconds);

            this.Endpoint = endpoint;

            this.CertificateThumbprint = certificateThumbprint;
            this.CertificateStoreLocation = certificateStoreLocation;
            this.Certificate = certificate;
            this.Timeout = timeout;

            if (mdmEnvironment != MdmEnvironment.Production && mdmEnvironment != MdmEnvironment.Int)
            {
                throw new ArgumentException($"The parameter {nameof(mdmEnvironment)} has an invalid value {mdmEnvironment}");
            }

            this.mdmEnvironmentMapIndex = (int)mdmEnvironment;
        }

        /// <summary>
        /// Gets or sets the frequency in which the automatic refresh of home stamps is going to be performed (if any refresh is being performed).
        /// </summary>
        public static TimeSpan HomeStampAutomaticUpdateFrequency { get; set; } = TimeSpan.FromMinutes(10);

        /// <summary>
        /// Gets or sets the frequency in which the automatic refresh of home stamp host to IP map is going to be performed.
        /// </summary>
        public static TimeSpan DnsResolutionUpdateFrequency { get; set; } = TimeSpan.FromSeconds(4);

        /// <summary>
        /// Gets a value indicating whether to use AAD user authentication.
        /// </summary>
        public bool UseAadUserAuthentication { get; }

        /// <summary>
        /// Gets a value indicating whether this instance is global endpoint.
        /// </summary>
        public bool IsGlobalEndpoint
        {
            get
            {
                if (this.Endpoint == null)
                {
                    return false;
                }

                if (this.isGlobalEndpoint.HasValue)
                {
                    return this.isGlobalEndpoint.Value;
                }

                if (globalEnvironments == null)
                {
                    throw new MetricsClientException("The global environments haven't been resolved yet.");
                }

                foreach (var ge in globalEnvironments)
                {
                    if (this.Endpoint.Host.Equals(new Uri(ge).Host, StringComparison.OrdinalIgnoreCase))
                    {
                        this.isGlobalEndpoint = true;
                        break;
                    }
                }

                if (this.isGlobalEndpoint != true)
                {
                    this.isGlobalEndpoint = false;
                }

                return this.isGlobalEndpoint.Value;
            }
        }

        /// <summary>
        /// Gets or sets the additional default request headers used for internal testing.
        /// </summary>
        public Dictionary<string, string> AdditionalDefaultRequestHeaders { get; set; }

        /// <summary>
        /// Gets the endpoint pointed by this instance, typically null, only not null when using an instance .
        /// </summary>
        public Uri Endpoint { get; }

        /// <summary>
        /// Gets the certificate used to authenticate with MDM.
        /// </summary>
        public X509Certificate2 Certificate
        {
            get
            {
                if (this.certificate == null)
                {
                    this.certificate = CertificateHelper.FindAndValidateCertificate(this.CertificateThumbprint, this.CertificateStoreLocation);
                }

                return this.certificate;
            }

            private set
            {
                this.certificate = value;
            }
        }

        /// <summary>
        /// Gets the certificate thumbprint.
        /// </summary>
        public string CertificateThumbprint
        {
            get
            {
                if (this.certificate != null)
                {
                    this.certificateThumbprint = this.certificate.Thumbprint;
                }

                return this.certificateThumbprint;
            }

            private set
            {
                this.certificateThumbprint = value;
            }
        }

        /// <summary>
        /// Gets the MDM stamp being used by the connection object.
        /// </summary>
        public MdmEnvironment MdmEnvironment
        {
            get
            {
                if (this.Endpoint != null)
                {
                    throw new InvalidOperationException($"Endpoint was specified during construction of instance, this instance operates only against that given endpoint {this.Endpoint}.");
                }

                return (MdmEnvironment)this.mdmEnvironmentMapIndex;
            }
        }

        /// <summary>
        /// Gets the certificate store location.
        /// </summary>
        public StoreLocation CertificateStoreLocation { get; private set; }

        /// <summary>
        /// Gets the time to wait before the request times out.
        /// </summary>
        public TimeSpan Timeout { get; private set;  }

        /// <summary>
        /// Gets or sets a value indicating whether to disable DNS resolution for unit test.
        /// </summary>
        internal static bool DisableDnsResolutionForUnitTest { get; set; }

        /// <summary>
        /// Gets the endpoint for a given MDM monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account for which we want to retrieve the endpoint.</param>
        /// <returns>Returns the URI of the given account, or the fixed endpoint if one was specified at construction time.</returns>
        public Uri GetEndpoint(string monitoringAccount)
        {
            if (this.Endpoint != null)
            {
                // This instance is fixed on a endpoint always return that endpoint.
                return this.Endpoint;
            }

            StampInformation endpoint = this.GetAndUpdateIfRequiredStampInformation(monitoringAccount);
            return endpoint.StampMainUri;
        }

        /// <summary>
        /// Gets the endpoint for querying metrics data information of MDM monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account for which we want to retrieve the endpoint.</param>
        /// <returns>Returns the URI of the given account, or the fixed endpoint if one was specified at construction time.</returns>
        public Uri GetMetricsDataQueryEndpoint(string monitoringAccount)
        {
            if (this.Endpoint != null)
            {
                // This instance is fixed on a endpoint always return that endpoint.
                return this.Endpoint;
            }

            if (this.UseAadUserAuthentication)
            {
                return this.GetEndpoint(monitoringAccount);
            }

            StampInformation endpoint = this.GetAndUpdateIfRequiredStampInformation(monitoringAccount);
            return endpoint.StampQueryUri;
        }

        /// <summary>
        /// Gets the global endpoint.
        /// </summary>
        /// <returns>Returns the URI of the global endpoint.</returns>
        public Uri GetGlobalEndpoint()
        {
            return new Uri(ResolveGlobalEnvironments()[this.mdmEnvironmentMapIndex]);
        }

        /// <summary>
        /// Resolves the base URIs to the known global environments, and its order must match the enum <see cref="MdmEnvironment" /> used to identify the desired
        /// global environment.
        /// </summary>
        /// <returns>The base URIs to the known global environments.</returns>
        internal static string[] ResolveGlobalEnvironments()
        {
            if (globalEnvironments != null)
            {
                return globalEnvironments;
            }

            lock (GlobalEnvironmentInitializationLock)
            {
                globalEnvironments = new[]
                {
                    $"https://{ProductionGlobalEnvironmentResolver.ResolveGlobalStampHostName()}",
                    "https://az-int.metrics.nsatc.net"
                };

                for (int i = 0; i < globalEnvironments.Length; i++)
                {
                    // Some accounts may have empty home gslb set in that case use the default stamp for the environment.
                    var defaultGlobalUri = new Uri(globalEnvironments[i]);
                    GslbToUris[i].TryAdd(string.Empty, defaultGlobalUri);
                }

                return globalEnvironments;
            }
        }

        /// <summary>
        /// Resolves the ip for <paramref name="hostname"/>.
        /// </summary>
        /// <param name="hostname">The hostname.</param>
        /// <param name="throwOnFailure">if set to <c>true</c> [throw on failure].</param>
        /// <returns>The IPv4 address.</returns>
        internal static async Task<string> ResolveIp(string hostname, bool throwOnFailure)
        {
            try
            {
                IPAddress[] addresslist = await Dns.GetHostAddressesAsync(hostname).ConfigureAwait(false);

                // use only IPv4 for now.
                var resolvedIp = addresslist?.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork)?.ToString();
                if (string.IsNullOrWhiteSpace(resolvedIp))
                {
                    throw new Exception($"Resolved IP is null or empty. Addresslist:{JsonConvert.SerializeObject(addresslist)}.");
                }

                return resolvedIp;
            }
            catch (Exception e)
            {
                Logger.Log(
                    LoggerLevel.Error,
                    LogId,
                    "ResolveIp",
                    $"Resolving hostname to IP got an exception. HostName:{hostname}, Exception:{e}");

                if (throwOnFailure)
                {
                    throw new MetricsClientException($"Resolving {hostname} to IP got an exception.", e);
                }

                return null;
            }
        }

        /// <summary>
        /// Attempts to get the IP address from the cache. If unable it will resolve the IP and cache the value prior to returning.
        /// </summary>
        /// <param name="endpoint">The endpoint.</param>
        /// <returns>The ip url for the endpoint.</returns>
        internal static async Task<Uri> GetCachedIpAddress(Uri endpoint)
        {
            if (DisableDnsResolutionForUnitTest)
            {
                return endpoint;
            }

            if (endpoint.Host == "metrics-ob.dc.ad.msft.net")
            {
                // TODO: it must be some devbox setup issue that caused 127.0.0.1:44300 not to work.
                return endpoint;
            }

            Uri ipUri;
            if (HostToIpUriMap.TryGetValue(endpoint, out ipUri))
            {
                return ipUri;
            }

            var resolvedIp = await ResolveIp(endpoint.Host, throwOnFailure: true).ConfigureAwait(false);
            ipUri = new Uri($"https://{resolvedIp}:{endpoint.Port}");
            HostToIpUriMap.TryAdd(endpoint, ipUri);
            return ipUri;
        }

        /// <summary>
        /// Gets the relative URL depending on authentication.
        /// </summary>
        /// <param name="relativeUrl">The relative URL without authentication consideration.</param>
        /// <returns>The relative URL considering authentication type.</returns>
        internal string GetAuthRelativeUrl(string relativeUrl)
        {
           return this.UseAadUserAuthentication ? UserApiFirstSegment + relativeUrl : CertApiFirstSegment + relativeUrl;
        }

        /// <summary>
        /// Check this instance's field <see cref="isGlobalEndpoint" /> by handling exception with retry.
        /// </summary>
        /// <returns>The value of <see cref="isGlobalEndpoint" /> with retry.</returns>
        internal bool CheckIfGlobalEndpointWithRetry()
        {
            try
            {
                return this.IsGlobalEndpoint;
            }
            catch (MetricsClientException)
            {
            }

            ResolveGlobalEnvironments();
            return this.IsGlobalEndpoint;
        }

        private static async void RefreshIpAddresses()
        {
            try
            {
                Logger.Log(
                    LoggerLevel.Info,
                    LogId,
                    "RefreshIpAddresses",
                    "Initiated the automatic refresh of IP addresses of home stamp endpoints.");

                if (globalEnvironments == null)
                {
                    ResolveGlobalEnvironments();
                }

                foreach (var kvp in HostToIpUriMap)
                {
                    var hostname = kvp.Key.Host;
                    var resolvedIp = await ResolveIp(hostname, throwOnFailure: false).ConfigureAwait(false);
                    if (!string.IsNullOrWhiteSpace(resolvedIp))
                    {
                        var existingIp = kvp.Value.Host;
                        if (resolvedIp != existingIp)
                        {
                            HostToIpUriMap.TryUpdate(kvp.Key, new Uri($"https://{resolvedIp}:{kvp.Key.Port}"), kvp.Value);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.Log(LoggerLevel.Error, LogId, "RefreshIpAddresses", $"Hit exception: {e}");
            }
            finally
            {
                if (DnsResolutionUpdateFrequency == TimeSpan.Zero)
                {
                    Logger.Log(LoggerLevel.Error, LogId, "RefreshIpAddresses", "Terminate the refresh task since the refresh frequency is 0");
                }
                else
                {
                    TimerToRefreshIpAddress.Change(DnsResolutionUpdateFrequency, System.Threading.Timeout.InfiniteTimeSpan);
                }
            }
        }

        /// <summary>
        /// Helper method used to set-up the map from account to home stamp URI when endpoint is not explicitly set. This method
        /// is intended to be called from an instance constructor and it will use the certificate of that instance to retrieve the
        /// home stamp URI from the respective global environment. This method should throw any exception that it receives while attempting
        /// to build the home stamp map for the accounts.
        /// </summary>
        private static async void RefreshAccountHomeStamp()
        {
            if (globalEnvironments == null)
            {
                Logger.Log(LoggerLevel.Error, LogId, "RefreshAccountHomeStamp", "The global environments haven't been resolved yet.");
                return;
            }

            for (int index = 0; index < NumberOfMdmGlobalEnvironments; index++)
            {
                try
                {
                    await UpdateAccountToUriMapAsync(HttpClientWithoutAuthentication, globalEnvironments[index], GslbToUris[index], AccountToUriMaps[index]).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    Logger.Log(
                        LoggerLevel.Error,
                        LogId,
                        "RefreshAccountHomeStamp",
                        $"The periodic background task that updates the account to URI map got an exception. Environment:{globalEnvironments[index]}, Exception:{e}");
                }
            }

            if (HomeStampAutomaticUpdateFrequency == TimeSpan.Zero)
            {
                Logger.Log(LoggerLevel.Error, LogId, "RefreshAccountHomeStamp", "Terminate the home stamp refresh task since the refresh frequency is 0");
            }
            else
            {
                TimerToRefreshHomeStamp.Change(HomeStampAutomaticUpdateFrequency, System.Threading.Timeout.InfiniteTimeSpan);
            }
        }

        /// <summary>
        /// Helper method that updates the targeted map with the most recent information from the given global stamp.
        /// </summary>
        /// <param name="httpClient">The HTTP client helper to be used to communicate with the global stamp.</param>
        /// <param name="globalEnvironmentUrl">The request URL to be used on the request to retrieve the accounts.</param>
        /// <param name="gslbToUris">A dictionary that maps home gslb strings to Uri objects to avoid duplicate Uri objects.</param>
        /// <param name="targetMap">The map to be updated with the results of the method run.</param>
        /// <returns>
        /// A task that can be used to synchronize the completion of the async method.
        /// </returns>
        private static async Task UpdateAccountToUriMapAsync(
            HttpClient httpClient,
            string globalEnvironmentUrl,
            ConcurrentDictionary<string, Uri> gslbToUris,
            ConcurrentDictionary<string, StampInformation> targetMap)
        {
            Logger.Log(
                LoggerLevel.Info,
                LogId,
                "UpdateAccountToUriMapAsync",
                "Initiated the automatic refresh of the account home stamp endpoints.");

            foreach (var account in targetMap.Keys)
            {
                try
                {
                    await GetAndUpdateStampInfoAsync(httpClient, globalEnvironmentUrl, gslbToUris, targetMap, account).ConfigureAwait(false);
                }
                catch (Exception e)
                {
                    // Exception treated for each call so a single bad/corrupt account does not preclude the update of the others. It is possible
                    // that this will log a few accounts in case of lost connection but it seems preferable to have a single account failure (think about
                    // a single account failing constantly) blocking the update of others.
                    Logger.Log(
                        LoggerLevel.Error,
                        LogId,
                        "AccountToUriUpdate",
                        "The periodic background task that updates the account to URI map got an exception for account {0}. URL:{1}, Exception:{2}",
                        account,
                        globalEnvironmentUrl,
                        e);
                }
            }
        }

        /// <summary>
        /// Helper method that gets the URI for a given account and updates the mapping from account to home stamp.
        /// </summary>
        /// <param name="httpClient">Helper object to be used in the operation.</param>
        /// <param name="globalStampUrl">Global stamp to be targeted in the operation.</param>
        /// <param name="gslbToUris">Stores all created home stamp URI objects in order to avoid duplications.</param>
        /// <param name="targetMap">Map to be updated with the latest information retrived remotely.</param>
        /// <param name="account">Account for which the URI should be retrieved.</param>
        /// <returns>
        /// The URI for the requested account.
        /// </returns>
        private static async Task<StampInformation> GetAndUpdateStampInfoAsync(HttpClient httpClient, string globalStampUrl, ConcurrentDictionary<string, Uri> gslbToUris, ConcurrentDictionary<string, StampInformation> targetMap, string account)
        {
            var requestUrl = $"{globalStampUrl}/public/monitoringAccount/{account}/homeStamp";
            var response = await HttpClientHelper.GetResponse(new Uri(requestUrl), HttpMethod.Get, httpClient, null, null).ConfigureAwait(false);
            var homeStampGslbHostname = JsonConvert.DeserializeObject<string>(response.Item1);
            string queryEndpoint = null;

            if (!string.IsNullOrEmpty(homeStampGslbHostname))
            {
                queryEndpoint = await SafeGetStampMetricDataQueryEndpointHostNameAsync(httpClient, "https://" + homeStampGslbHostname).ConfigureAwait(false);
            }

            Uri homeGslbUri;
            var homeGslbKey = string.IsNullOrWhiteSpace(homeStampGslbHostname) ? string.Empty : homeStampGslbHostname;
            if (!gslbToUris.TryGetValue(homeGslbKey, out homeGslbUri))
            {
                homeGslbUri = new Uri("https://" + homeStampGslbHostname);
                gslbToUris.AddOrUpdate(homeGslbKey, homeGslbUri, (_, uri) => homeGslbUri);
            }

            Uri queryEndpointUri;
            if (!string.IsNullOrEmpty(queryEndpoint))
            {
                queryEndpointUri = new Uri("https://" + queryEndpoint);
            }
            else
            {
                queryEndpointUri = homeGslbUri;
            }

            var stampInfo = new StampInformation(homeGslbUri, queryEndpointUri);
            targetMap.AddOrUpdate(
                account,
                stampInfo,
                (_, uri) => stampInfo);

            return stampInfo;
        }

        /// <summary>
        /// Helper method that gets the metric data query endpoint for the given mdm stamp.
        /// Return string.empty if no seperate query endpoint is configured.
        /// </summary>
        /// <param name="httpClient">Helper object to be used in the operation.</param>
        /// <param name="stampUrl">Url of the mdm stamp for which query endpoint needs to be discovered.</param>
        /// <returns>
        /// The URI for the requested account.
        /// </returns>
        private static async Task<string> SafeGetStampMetricDataQueryEndpointHostNameAsync(HttpClient httpClient, string stampUrl)
        {
            try
            {
                string requestUrl = $"{stampUrl}/public/metricsDataQueryEndpointHostName";
                Tuple<string, HttpResponseMessage> response = await HttpClientHelper.GetResponse(new Uri(requestUrl), HttpMethod.Get, httpClient, null, null).ConfigureAwait(false);
                var queryEndpoint = JsonConvert.DeserializeObject<string>(response.Item1);

                return queryEndpoint;
            }
            catch (Exception exp)
            {
                var httpException = exp as MetricsClientException;
                if (httpException?.ResponseStatusCode == HttpStatusCode.NotFound)
                {
                    Logger.Log(
                        LoggerLevel.Info,
                        LogId,
                        nameof(SafeGetStampMetricDataQueryEndpointHostNameAsync),
                        "Failed to resolve query endpoint for the mdm stamp due to query resolution endpoint not found, will use main endpoint.");
                }
                else
                {
                    Logger.Log(
                        LoggerLevel.Warning,
                        LogId,
                        nameof(SafeGetStampMetricDataQueryEndpointHostNameAsync),
                        "Failed to resolve query endpoint for the mdm stamp due to unexpected exception, will use main endpoint.",
                        exp);
                }

                return string.Empty;
            }
        }

        /// <summary>
        /// Gets the endpoint for querying metrics data information of MDM monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account for which we want to retrieve the endpoint.</param>
        /// <returns>Returns the URI of the given account, or the fixed endpoint if one was specified at construction time.</returns>
        private StampInformation GetAndUpdateIfRequiredStampInformation(string monitoringAccount)
        {
            StampInformation endpoint;
            if (!AccountToUriMaps[this.mdmEnvironmentMapIndex].TryGetValue(monitoringAccount, out endpoint))
            {
                // In this case we need to block until operation is completed since this is the information requested by the user.
                endpoint = GetAndUpdateStampInfoAsync(
                    HttpClientWithoutAuthentication,
                    ResolveGlobalEnvironments()[this.mdmEnvironmentMapIndex],
                    GslbToUris[this.mdmEnvironmentMapIndex],
                    AccountToUriMaps[this.mdmEnvironmentMapIndex],
                    monitoringAccount).GetAwaiter().GetResult();
            }

            return endpoint;
        }

        private struct StampInformation
        {
            public StampInformation(Uri stampMainUri, Uri stampQueryUri)
            {
                this.StampMainUri = stampMainUri;
                this.StampQueryUri = stampQueryUri;
            }

            public Uri StampMainUri { get; }

            public Uri StampQueryUri { get; }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Reducer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    /// <summary>
    /// Reducing function to use on time series data.
    /// </summary>
    public enum Reducer
    {
        /// <summary>
        /// Reducer not specified.  Query is not considered valid.
        /// </summary>
        Undefined,

        /// <summary>
        /// Not an reducer per se, it means that any value on the series should be evaluated by the <see cref="Operator"/>.
        /// </summary>
        Any,

        /// <summary>
        /// Not an reducer per se, it means that all values on the series should be evaluated by the <see cref="Operator"/>.
        /// The evaluation is relaxed in the sense that missing data points (null) are going to be considered
        /// as satisfying the <see cref="Operator"/> condition, but at least one data point should exist satisfying the <see cref="Operator"/>.
        /// </summary>
        All,

        /// <summary>
        /// Not an reducer per se, it means that all values on the series should be evaluated by the <see cref="Operator"/>.
        /// The evaluation is strict in the sense that missing data points (null) are going to be considered
        /// as not satisfying the <see cref="Operator"/> conditions.
        /// </summary>
        AllStrict,

        /// <summary>
        /// Reduce by calculating the average of all values in the series. Only data points different than
        /// null are considered for the average calculation.
        /// </summary>
        Average,

        /// <summary>
        /// Reduce by adding all values in the series.
        /// </summary>
        Sum,

        /// <summary>
        /// Reduce by selecting the minimum value of the series.
        /// </summary>
        Min,

        /// <summary>
        /// Reduce by selecting the maximum value of the series.
        /// </summary>
        Max
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PreaggregateFiltersManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.PreaggregateFiltersManagement
{
    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;
    using MetricEnrichmentRuleManagement;
    using Newtonsoft.Json;
    using Utility;

    /// <summary>
    /// This class manages get and save operations on preaggregate filters.
    /// </summary>
    public sealed class PreaggregateFiltersManager
    {
        private readonly ConnectionInfo connectionInfo;
        private readonly HttpClient httpClient;
        private readonly string configurationUrlPrefix;

        /// <summary>
        /// Initializes a new instance of the <see cref="PreaggregateFiltersManager"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the MDM endpoint being used.</param>
        public PreaggregateFiltersManager(ConnectionInfo connectionInfo)
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            this.connectionInfo = connectionInfo;
            this.configurationUrlPrefix = this.connectionInfo.GetAuthRelativeUrl("v1/config/preaggregate/dimensionfilters/");

            this.httpClient = HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo);
        }

        /// <summary>
        /// Gets all filters satisfying the given constraints.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="preaggregateDimensionNames">The preaggregate dimension names.</param>
        /// <param name="count">The count of filters requested. Use 0 to get all filters.</param>
        /// <param name="pageOffset">The offset of the requested filters page calculated based on the count of data returned.</param>
        /// <returns>All enrichment rules for the given monitoring account.</returns>
        public async Task<PreaggregateFilters> GetPreaggregateFiltersAsync(
            string monitoringAccount,
            string metricNamespace,
            string metricName,
            IEnumerable<string> preaggregateDimensionNames,
            int count,
            int pageOffset)
        {
            if (string.IsNullOrWhiteSpace(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var arguments = new RawPreaggregateFilterQueryArguments(monitoringAccount, metricNamespace, metricName, preaggregateDimensionNames, count, pageOffset);

            string serializedArguments = JsonConvert.SerializeObject(arguments);
            string path = $"{this.configurationUrlPrefix}monitoringAccount/{monitoringAccount}/getfilters";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount))
            {
                Path = path
            };

            var response = await HttpClientHelper.GetResponseAsStringAsync(
                uriBuilder.Uri,
                HttpMethod.Post,
                this.httpClient,
                monitoringAccount,
                this.configurationUrlPrefix,
                serializedContent: serializedArguments).ConfigureAwait(false);

            var rules = JsonConvert.DeserializeObject<PreaggregateFilters>(response);
            return rules;
        }

        /// <summary>
        /// Adds the given pre-aggregate filters to storage for given monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account for the enrichment rule.</param>
        /// <param name="preaggregateFilters">Preaggregate filters which needs to be added to the storage.</param>
        /// <returns>Task representing the operation.</returns>
        public async Task AddPreaggregateFilters(string monitoringAccount, PreaggregateFilters preaggregateFilters)
        {
            if (string.IsNullOrEmpty(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (preaggregateFilters == null)
            {
                throw new ArgumentNullException(nameof(preaggregateFilters));
            }

            string path = $"{this.configurationUrlPrefix}monitoringAccount/{monitoringAccount}/addfilters";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount))
            {
                Path = path,
                Query = "apiVersion=1"
            };

            string serializedContent = JsonConvert.SerializeObject(preaggregateFilters);

            await HttpClientHelper.GetResponseAsStringAsync(
                uriBuilder.Uri,
                HttpMethod.Post,
                this.httpClient,
                monitoringAccount,
                this.configurationUrlPrefix,
                serializedContent: serializedContent).ConfigureAwait(false);
        }

        /// <summary>
        /// Removes the given pre-aggregate filters from storage for given monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account for the enrichment rule.</param>
        /// <param name="preaggregateFilters">Preaggregate filters which needs to be removed from the storage.</param>
        /// <returns>Task representing the operation.</returns>
        public async Task RemovePreaggregateFilters(string monitoringAccount, PreaggregateFilters preaggregateFilters)
        {
            if (string.IsNullOrEmpty(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (preaggregateFilters == null)
            {
                throw new ArgumentNullException(nameof(preaggregateFilters));
            }

            string path = $"{this.configurationUrlPrefix}monitoringAccount/{monitoringAccount}/removefilters";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount))
            {
                Path = path,
                Query = "apiVersion=1"
            };

            string serializedContent = JsonConvert.SerializeObject(preaggregateFilters);

            await HttpClientHelper.GetResponseAsStringAsync(
                uriBuilder.Uri,
                HttpMethod.Delete,
                this.httpClient,
                monitoringAccount,
                this.configurationUrlPrefix,
                serializedContent: serializedContent).ConfigureAwait(false);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IFilteredTimeSeries.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System.Collections.Generic;
    using Metrics;
    using Microsoft.Online.Metrics.Serialization.Configuration;

    /// <summary>
    /// An interface for filtered time series.
    /// </summary>
    public interface IFilteredTimeSeries
    {
        /// <summary>
        /// Gets the metric identifier.
        /// </summary>
        MetricIdentifier MetricIdentifier { get; }

        /// <summary>
        /// Set of valid dimension name-value pairs that meet the query condition.
        /// </summary>
        IReadOnlyList<KeyValuePair<string, string>> DimensionList { get; }

        /// <summary>
        /// Gets the evaluated value for this time series that meets the condition set in the query (provided for evidence and/or sorting).
        /// </summary>
        double EvaluatedResult { get; }

        /// <summary>
        /// Gets the full collection time series values for the query interval. It should be null if
        /// the query did not request the full collection of values to be returned.
        /// </summary>
        /// <remarks>double.NaN is the sentinel used to indicate there is no metric value.</remarks>
        IReadOnlyList<KeyValuePair<SamplingType, double[]>> TimeSeriesValues { get; }
    }
}<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="IMonitorConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Monitors
{
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Configuration;

    /// <summary>
    /// The interface of managing monitor configuration.
    /// </summary>
    internal interface IMonitorConfigurationManager
    {
        /// <summary>
        /// Sync all monitor configuration across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <param name="validate">Whether to validate the monitor configuration in target accounts.</param>
        /// <returns>A list of configuration update result.</returns>
        Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false,
            bool validate = true);

        /// <summary>
        /// Sync all monitor configuration under specific namespace across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="metricNamespace">Metric namespace.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <param name="validate">Whether to validate the monitor configuration in target accounts.</param>
        /// <returns>A list of configuration update result.</returns>
        Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            bool skipVersionCheck = false,
            bool validate = true);

        /// <summary>
        /// Sync monitor configuration under specific metric across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="metricNamespace">Metric namespace.</param>
        /// <param name="metricName">Metric name.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <param name="validate">Whether to validate the monitor configuration in target accounts.</param>
        /// <returns>A list of configuration update result.</returns>
        Task<ConfigurationUpdateResultList> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            string metricName,
            bool skipVersionCheck = false,
            bool validate = true);
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FilteredQueryResponseDeserializer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using Online.Metrics.Serialization;
    using Query;

    /// <summary>
    /// The class to deserialize the binary payload in response.
    /// </summary>
    public sealed class FilteredQueryResponseDeserializer
    {
        /// <summary>
        /// Deserializes the specified stream.
        /// </summary>
        /// <param name="stream">The stream to deserialize.</param>
        /// <returns>
        /// A list of <see cref="FilteredTimeSeriesQueryResponse"/>.
        /// </returns>
        public static IReadOnlyList<IFilteredTimeSeriesQueryResponse> Deserialize(Stream stream)
        {
            using (var reader = new BinaryReader(stream))
            {
                var numOfResponses = (int)SerializationUtils.ReadUInt32FromBase128(reader);
                var results = new List<FilteredTimeSeriesQueryResponse>(numOfResponses);
                for (int i = 0; i < numOfResponses; i++)
                {
                    // Strip off a version added by query service host - QueryCoordinatorHost.cs.
                    var version = reader.ReadByte();

                    var numOfQueryResults = reader.ReadInt32();
                    for (int k = 0; k < numOfQueryResults; k++)
                    {
                        var filteredTimeSeriesQueryResponse = new FilteredTimeSeriesQueryResponse();
                        filteredTimeSeriesQueryResponse.Deserialize(reader);
                        results.Add(filteredTimeSeriesQueryResponse);
                    }
                }

                return results;
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FilteredTimeSeriesQueryResponse.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Runtime.CompilerServices;
    using Metrics;

    using Microsoft.Cloud.Metrics.Client.Configuration;

    using Newtonsoft.Json;
    using Online.Metrics.Serialization;
    using Online.Metrics.Serialization.Configuration;
    using Utility;

    /// <summary>
    /// TopN query response corresponding to <see cref="FilteredTimeSeriesQueryRequest" />.
    /// </summary>
    public sealed class FilteredTimeSeriesQueryResponse : IFilteredTimeSeriesQueryResponse
    {
        /// <summary>
        /// The version to indicate complete failure of a <see cref="FilteredTimeSeriesQueryResponse"/>.
        /// </summary>
        public const byte VersionToIndicateCompleteFailure = byte.MaxValue;

        /// <summary>
        /// The current stable version.
        /// </summary>
        public const byte CurrentVersion = 3;

        /// <summary>
        /// The next version to be supported in the future, if different from <see cref="CurrentVersion"/>.
        /// </summary>
        /// <remarks>
        /// Put version change history here.
        /// The initial version starts with 1.
        /// Version 2: Serializer and deserializer can process query messages in query result.
        /// Version 3: Handle more than 1 time series metadata as required in the support of multi-account query.
        /// </remarks>
        public const byte NextVersion = 3;

        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeriesQueryResponse"/> class.
        /// </summary>
        public FilteredTimeSeriesQueryResponse()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeriesQueryResponse" /> class.
        /// </summary>
        /// <param name="startTime">Start time</param>
        /// <param name="endTime">End time</param>
        /// <param name="timeResolutionInMinutes">Time resolution in mins</param>
        /// <param name="filteredTimeSeriesList">The time series returned</param>
        /// <param name="errorMessage">The error message.</param>
        internal FilteredTimeSeriesQueryResponse(
            DateTime startTime,
            DateTime endTime,
            int timeResolutionInMinutes,
            IReadOnlyList<FilteredTimeSeries> filteredTimeSeriesList,
            string errorMessage = null)
        {
            this.StartTimeUtc = startTime;
            this.EndTimeUtc = endTime;
            this.TimeResolutionInMinutes = timeResolutionInMinutes;
            this.FilteredTimeSeriesList = filteredTimeSeriesList;
            this.DiagnosticInfo = new DiagnosticInfo { ErrorMessage = errorMessage };
        }

        /// <summary>
        /// Gets the query request.
        /// </summary>
        public FilteredTimeSeriesQueryRequest QueryRequest { get; private set; }

        /// <summary>
        /// Gets the end time in UTC for the query results.
        /// </summary>
        public DateTime EndTimeUtc { get; private set; }

        /// <summary>
        /// Gets the start time in UTC for the query results.
        /// </summary>
        public DateTime StartTimeUtc { get; private set; }

        /// <summary>
        /// Gets the time resolution in milliseconds for the query results.
        /// </summary>
        public int TimeResolutionInMinutes { get; private set; }

        /// <summary>
        /// Gets the <see cref="FilteredTimeSeries"/> list. Each item represents a single time series where start time, end time and time resolution
        /// is represented by this object members.
        /// </summary>
        public IReadOnlyList<IFilteredTimeSeries> FilteredTimeSeriesList { get; private set; }

        /// <summary>
        /// Gets the error code.
        /// </summary>
        public FilteredTimeSeriesQueryResponseErrorCode ErrorCode { get; private set; } = FilteredTimeSeriesQueryResponseErrorCode.Success;

        /// <summary>
        /// Gets the diagnostics information.
        /// </summary>
        public DiagnosticInfo DiagnosticInfo { get; private set; }

        /// <summary>
        /// Deserializes to populate this object.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public void Deserialize(BinaryReader reader)
        {
            byte version;
            uint resultTimeSeriesCount;
            Dictionary<int, string> stringTable;
            long stringTableLengthInByte;
            SeriesMetadata seriesMetadata;
            Dictionary<int, SeriesMetadata> metadataTable;
            long metadataTableLengthInByte;

            if (!this.ReadPreamble(
                reader,
                out version,
                out resultTimeSeriesCount,
                out stringTable,
                out stringTableLengthInByte,
                out seriesMetadata,
                out metadataTable,
                out metadataTableLengthInByte))
            {
                return;
            }

            var filteredTimeSeriesList = new FilteredTimeSeries[resultTimeSeriesCount];
            for (int i = 0; i < resultTimeSeriesCount; i++)
            {
                filteredTimeSeriesList[i] = ReadTimeSeries(version, reader, seriesMetadata, stringTable, metadataTable);
            }

            this.FilteredTimeSeriesList = filteredTimeSeriesList;

            // we have read the string table and metadata tables, so just skip them to move to the correct position.
            reader.BaseStream.Position += metadataTableLengthInByte + stringTableLengthInByte;

            // additional query messages in version 2 and above
            // For now, we just read query messages. Further decisions need to be made on how to store messages
            if (version > 1)
            {
                int messageCount = reader.ReadByte();
                for (int i = 0; i < messageCount; i++)
                {
                    int topic = reader.ReadByte();
                    int level = reader.ReadByte();
                    int source = reader.ReadByte();
                    string content = reader.ReadString();
                }
            }
        }

        /// <summary>
        /// Deserializes to populate this object.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>The IEnumerable with FilteredTimeSeries.</returns>
        public IEnumerable<FilteredTimeSeries> ReadFilteredTimeSeries(BinaryReader reader)
        {
            var numOfResponses = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            for (int j = 0; j < numOfResponses; j++)
            {
                // Strip off a version added by query service host - QueryCoordinatorHost.cs.
                reader.ReadByte();

                var numOfQueryResults = reader.ReadInt32();
                for (int k = 0; k < numOfQueryResults; k++)
                {
                    byte version;
                    uint resultTimeSeriesCount;
                    Dictionary<int, string> stringTable;
                    long stringTableLengthInByte;
                    SeriesMetadata seriesMetadata;
                    Dictionary<int, SeriesMetadata> metadataTable;
                    long metadataTableLengthInByte;

                    if (!this.ReadPreamble(
                        reader,
                        out version,
                        out resultTimeSeriesCount,
                        out stringTable,
                        out stringTableLengthInByte,
                        out seriesMetadata,
                        out metadataTable,
                        out metadataTableLengthInByte))
                    {
                        yield break;
                    }

                    for (int i = 0; i < resultTimeSeriesCount; i++)
                    {
                        yield return ReadTimeSeries(version, reader, seriesMetadata, stringTable, metadataTable);
                    }
                }
            }
        }

        private static SeriesMetadata DeserializeTimeSeriesMetadata(BinaryReader reader, Dictionary<int, string> stringTable)
        {
            var metricIdentifier = new MetricIdentifier(
                DeserializeStringByIndex(reader, stringTable),
                DeserializeStringByIndex(reader, stringTable),
                DeserializeStringByIndex(reader, stringTable));

            var dimensionsCount = reader.ReadByte();
            var dimensionNames = new string[dimensionsCount];
            for (var i = 0; i < dimensionsCount; i++)
            {
                dimensionNames[i] = DeserializeStringByIndex(reader, stringTable);
            }

            return new SeriesMetadata(metricIdentifier, dimensionNames);
        }

        private static FilteredTimeSeries ReadTimeSeries(
            byte version,
            BinaryReader reader,
            SeriesMetadata seriesMetadata,
            Dictionary<int, string> stringTable,
            Dictionary<int, SeriesMetadata> metadataTable)
        {
            if (version >= 3)
            {
                var index = (int)SerializationUtils.ReadUInt32FromBase128(reader);
                seriesMetadata = metadataTable[index];
            }

            var metricIdentifier = seriesMetadata.MetricIdentifier;
            var dimensionNames = seriesMetadata.DimensionNames;

            var dimensionsCount = dimensionNames.Length;
            var dimensionList = new KeyValuePair<string, string>[dimensionsCount];
            for (int k = 0; k < dimensionsCount; k++)
            {
                dimensionList[k] = new KeyValuePair<string, string>(dimensionNames[k], DeserializeStringByIndex(reader, stringTable));
            }

            var propertiesCount = reader.ReadByte();
            var evaluatedValues = new KeyValuePair<string, double>[propertiesCount];
            for (int k = 0; k < propertiesCount; k++)
            {
                evaluatedValues[k] = new KeyValuePair<string, double>(DeserializeStringByIndex(reader, stringTable), reader.ReadDouble());
            }

            var samplingTypesCount = reader.ReadByte();
            var timeSeriesData = new KeyValuePair<SamplingType, double[]>[samplingTypesCount];
            for (int k = 0; k < samplingTypesCount; k++)
            {
                var samplingTypeString = DeserializeStringByIndex(reader, stringTable);
                var samplingType = SamplingType.BuiltInSamplingTypes.ContainsKey(samplingTypeString)
                    ? SamplingType.BuiltInSamplingTypes[samplingTypeString]
                    : new SamplingType(samplingTypeString);
                timeSeriesData[k] = new KeyValuePair<SamplingType, double[]>(samplingType, DoubleValueSerializer.Deserialize(reader));
            }

            double evaluatedResult = propertiesCount == 0 ? double.NaN : evaluatedValues[0].Value;
            return new FilteredTimeSeries(metricIdentifier, dimensionList, evaluatedResult, timeSeriesData);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static string DeserializeStringByIndex(BinaryReader reader, Dictionary<int, string> stringTable)
        {
            var index = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            return stringTable[index];
        }

        private bool ReadPreamble(
            BinaryReader reader,
            out byte version,
            out uint resultTimeSeriesCount,
            out Dictionary<int, string> stringTable,
            out long stringTableLengthInByte,
            out SeriesMetadata seriesMetadata,
            out Dictionary<int, SeriesMetadata> metadataTable,
            out long metadataTableLengthInByte)
        {
            resultTimeSeriesCount = 0;
            stringTable = null;
            stringTableLengthInByte = 0;
            seriesMetadata = null;
            metadataTable = null;
            metadataTableLengthInByte = 0;

            version = reader.ReadByte();

            if (version == 0)
            {
                throw new MetricsClientException(
                    $"The server didn't respond with the right version of serialization - the initial version is 1 but the server responds with version 0.");
            }

            this.DiagnosticInfo = new DiagnosticInfo();
            if (version == VersionToIndicateCompleteFailure)
            {
                this.ErrorCode = (FilteredTimeSeriesQueryResponseErrorCode)reader.ReadInt16();

                // The trace ID and others will be filled in by callers.
                this.DiagnosticInfo.ErrorMessage = reader.ReadString();

                bool returnRequestObjectOnFailure = reader.ReadBoolean();
                if (returnRequestObjectOnFailure)
                {
                    this.QueryRequest = JsonConvert.DeserializeObject<FilteredTimeSeriesQueryRequest>(reader.ReadString());
                }

                return false;
            }

            if (version > NextVersion)
            {
                throw new MetricsClientException(
                    $"The server didn't respond with the right version of serialization. CurrentVersion : {CurrentVersion}, NextVersion : {NextVersion}, Responded: {version}.");
            }

            var hasDataQualityInfo = reader.ReadBoolean();
            if (hasDataQualityInfo)
            {
                var queryResultQualityInfo = new QueryResultQualityInfo();
                queryResultQualityInfo.Deserialize(reader);
            }

            this.StartTimeUtc = new DateTime(
                (long)SerializationUtils.ReadUInt64FromBase128(reader) * TimeSpan.TicksPerMinute,
                DateTimeKind.Utc);
            this.EndTimeUtc = this.StartTimeUtc.AddMinutes(SerializationUtils.ReadUInt32FromBase128(reader));
            this.TimeResolutionInMinutes = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            resultTimeSeriesCount = SerializationUtils.ReadUInt32FromBase128(reader);

            /* Read string table. */
            var currentPosition = reader.BaseStream.Position;
            var stringTableRelativePosition = reader.ReadUInt64();
            reader.BaseStream.Position = currentPosition + (long)stringTableRelativePosition;

            var stringTableStartPostion = reader.BaseStream.Position;
            var stringTableSize = SerializationUtils.ReadUInt32FromBase128(reader);
            stringTable = new Dictionary<int, string>((int)stringTableSize);
            for (int i = 0; i < stringTableSize; i++)
            {
                stringTable.Add(i, reader.ReadString());
            }

            stringTableLengthInByte = reader.BaseStream.Position - stringTableStartPostion;

            // + sizeof (double) since we just read the stringTableRelativePosition as double.
            reader.BaseStream.Position = currentPosition + sizeof(double);

            if (version >= 3)
            {
                /* Read metadata table */
                currentPosition = reader.BaseStream.Position;
                var metadataTableRelativePosition = reader.ReadUInt64();
                reader.BaseStream.Position = currentPosition + (long)metadataTableRelativePosition;

                var metadataTableStartPostion = reader.BaseStream.Position;
                var metadataTableSize = SerializationUtils.ReadUInt32FromBase128(reader);

                metadataTable = new Dictionary<int, SeriesMetadata>((int)metadataTableSize);
                for (var i = 0; i < metadataTableSize; i++)
                {
                    metadataTable.Add(i, DeserializeTimeSeriesMetadata(reader, stringTable));
                }

                metadataTableLengthInByte = reader.BaseStream.Position - metadataTableStartPostion;

                // + sizeof (double) since we just read the metadataTableRelativePosition as double.
                reader.BaseStream.Position = currentPosition + sizeof(double);
            }

            if (resultTimeSeriesCount > 0)
            {
                if (version < 3)
                {
                    seriesMetadata = DeserializeTimeSeriesMetadata(reader, stringTable);
                    this.QueryRequest = new FilteredTimeSeriesQueryRequest(seriesMetadata.MetricIdentifier);
                }
                else
                {
                    this.QueryRequest = new FilteredTimeSeriesQueryRequest(metadataTable.Values.First().MetricIdentifier);
                }
            }

            return true;
        }

        private sealed class SeriesMetadata
        {
            public SeriesMetadata(MetricIdentifier metricIdentifier, string[] dimensionNames)
            {
                this.MetricIdentifier = metricIdentifier;
                this.DimensionNames = dimensionNames;
            }

            public MetricIdentifier MetricIdentifier { get; }

            public string[] DimensionNames { get; }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QueryResultQualityInfo.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading;

    /// <summary>
    /// Represents the query result quality information.
    /// </summary>
    /// <remarks>
    /// TODO: Add time based data loss support.
    /// </remarks>
    public sealed class QueryResultQualityInfo
    {
        private readonly ConcurrentDictionary<string, int> droppedTimeSeries = new ConcurrentDictionary<string, int>();
        private int totalDroppedTimeSeries;
        private int totalEstimatedTimeSeries;

        /// <summary>
        /// Gets or sets the total estimated time series for the query.
        /// </summary>
        public int TotalEstimatedTimeSeries
        {
            get { return this.totalEstimatedTimeSeries; }
            set { this.totalEstimatedTimeSeries = value; }
        }

        /// <summary>
        /// Gets or sets the total dropped time series for the query.
        /// </summary>
        public int TotalDroppedTimeSeries
        {
            get { return this.totalDroppedTimeSeries; }
        }

        /// <summary>
        /// Gets the total evaluated time series for the query.  This represents the number of raw series query
        /// service processed to obtain the customer-facing result.
        /// </summary>
        public int TotalEvaluatedTimeSeries
        {
            get { return this.TotalEstimatedTimeSeries - this.totalDroppedTimeSeries; }
        }

        /// <summary>
        /// Gets the dropped time series reasons.
        /// </summary>
        /// <remarks>
        /// Used for unit testing purposes.
        /// </remarks>
        /// <returns>Dropped time series reasons.</returns>
        public ICollection<string> GetDroppedTimeSeriesReasons()
        {
            return this.droppedTimeSeries.Keys;
        }

        /// <summary>
        /// Gets the dropped time series by reason.
        /// </summary>
        /// <param name="reason">The reason.</param>
        /// <remarks>
        /// Used for unit testing purposes.
        /// </remarks>
        /// <returns>Dropped time series by reason.</returns>
        public int GetDroppedTimeSeriesByReason(string reason)
        {
            return this.droppedTimeSeries[reason];
        }

        /// <summary>
        /// Register dropped time series during the query.
        /// </summary>
        /// <param name="reason">The reason for dropping the time series.</param>
        /// <param name="count">The count.</param>
        public void RegisterDroppedTimeSeries(string reason, int count)
        {
            if (count > 0)
            {
                Interlocked.Add(ref this.totalDroppedTimeSeries, count);
                this.droppedTimeSeries.AddOrUpdate(
                    reason,
                    count,
                    (key, existingValue) => existingValue + count);
            }
        }

        /// <summary>
        /// Registers the estimated time series during a query.  This is the
        /// number of time series we would ideally get back from the store.
        /// </summary>
        /// <param name="count">The count.</param>
        public void RegisterEstimatedTimeSeries(int count)
        {
            Interlocked.Add(ref this.totalEstimatedTimeSeries, count);
        }

        /// <summary>
        /// Returns a <see cref="string" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="string" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            string result = $"Total Estimated TimeSeries:{this.TotalEstimatedTimeSeries}, Total Dropped TimeSeries:{this.totalDroppedTimeSeries}.";
            if (this.totalDroppedTimeSeries > 0)
            {
                result = this.droppedTimeSeries.Aggregate(result, (current, dropReason) => current + $"{dropReason.Key}:{dropReason.Value}");
            }

            return result;
        }

        /// <summary>
        /// Deserializes query quality info from given stream reader.
        /// </summary>
        /// <param name="reader">The stream reader containing quality information.</param>
        public void Deserialize(BinaryReader reader)
        {
            // Any modifications here should be replicated to D:\onebranch\EngSys\MDA\MetricsAndHealth\src\DistributedQuery\Interfaces\QueryResultQuality.cs
            reader.ReadByte();
            this.TotalEstimatedTimeSeries = reader.ReadInt32();
            this.totalDroppedTimeSeries = reader.ReadInt32();
            if (this.totalDroppedTimeSeries > 0)
            {
                var numberOfReasons = reader.ReadInt32();
                for (int i = 0; i < numberOfReasons; i++)
                {
                    var reason = reader.ReadString();
                    var dropCount = reader.ReadInt32();
                    this.RegisterDroppedTimeSeries(reason, dropCount);
                }
            }
        }

        /// <summary>
        /// Serializes the quality info into given stream writer.
        /// </summary>
        /// <param name="writer">The stream writer.</param>
        public void Serialize(BinaryWriter writer)
        {
            // Any modifications here should be replicated to D:\onebranch\EngSys\MDA\MetricsAndHealth\src\DistributedQuery\Interfaces\QueryResultQuality.cs
            // Version
            writer.Write((byte)0);
            writer.Write(this.TotalEstimatedTimeSeries);
            writer.Write(this.totalDroppedTimeSeries);
            if (this.totalDroppedTimeSeries > 0)
            {
                writer.Write(this.droppedTimeSeries.Count);
                foreach (var dropReason in this.droppedTimeSeries)
                {
                    writer.Write(dropReason.Key);
                    writer.Write(dropReason.Value);
                }
            }
        }

        /// <summary>
        /// Aggregates the data from given qualty info.
        /// </summary>
        /// <param name="source">The source quality info.</param>
        public void Aggregate(QueryResultQualityInfo source)
        {
            if (source.totalDroppedTimeSeries > 0)
            {
                foreach (var reason in source.droppedTimeSeries)
                {
                    this.RegisterDroppedTimeSeries(reason.Key, reason.Value);
                }
            }
        }
    }
}
<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="IMonitoringAccountConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;
    using System.Threading.Tasks;

    /// <summary>
    /// This interface that manages monitoring account configurations.
    /// </summary>
    public interface IMonitoringAccountConfigurationManager
    {
        /// <summary>
        /// Get the monitoring account specified by the monitoring account name.
        /// </summary>
        /// <param name="monitoringAccountName">The name of the monitoring account.</param>
        /// <returns>The monitoring account.</returns>
        Task<IMonitoringAccount> GetAsync(string monitoringAccountName);

        /// <summary>
        /// Creates a monitoring account with provided configuration.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration.</param>
        /// <param name="stampHostName">The stamp name such as prod3.metrics.nsatc.net as documented @ https://aka.ms/mdm-endpoints.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task CreateAsync(IMonitoringAccount monitoringAccount, string stampHostName);

        /// <summary>
        /// Create a new monitoring account named <paramref name="newMonitoringAccountName"/> on stamp <paramref name="stampHostName"/> by copying the common settings from <paramref name="monitoringAccountToCopyFrom" />.
        /// </summary>
        /// <param name="newMonitoringAccountName">The new monitoring account name.</param>
        /// <param name="monitoringAccountToCopyFrom">The name of the monitoring account where common settings are copied from.</param>
        /// <param name="stampHostName">The stamp name such as prod3.metrics.nsatc.net as documented @ https://aka.ms/mdm-endpoints.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task CreateAsync(string newMonitoringAccountName, string monitoringAccountToCopyFrom, string stampHostName);

        /// <summary>
        /// Save the monitoring account configuration provided.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration to save.</param>
        /// <param name="skipVersionCheck">Flag indicating whether or not the version flag should be honored.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task SaveAsync(IMonitoringAccount monitoringAccount, bool skipVersionCheck = false);

        /// <summary>
        /// Delete the monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration to delete.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task DeleteAsync(string monitoringAccount);

        /// <summary>
        /// Un-Delete the monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration to un-delete.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task UnDeleteAsync(string monitoringAccount);

        /// <summary>
        /// Synchronizes the monitoring account configuration asynchronous.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="skipVersionCheck">if set to <c>true</c> [skip version check].</param>
        /// <returns>A list of <see cref="ConfigurationUpdateResult"/>.</returns>
        Task<IReadOnlyList<ConfigurationUpdateResult>> SyncMonitoringAccountConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false);
    }
}<file_sep>﻿// <copyright file="ConfigurationUpdateResultList.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;

    /// <summary>
    /// Stores list of configuration update result for a configuration.
    /// </summary>
    public sealed class ConfigurationUpdateResultList
    {
        /// <summary>
        /// Gets or sets the monitoring account.
        /// </summary>
        public string MonitoringAccount { get; set; }

        /// <summary>
        /// Gets or sets the metric namespace.
        /// </summary>
        public string MetricNamespace { get; set; }

        /// <summary>
        /// Gets or sets the name of the metric.
        /// </summary>
        public string MetricName { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="ConfigurationUpdateResultList"/> is success.
        /// </summary>
        public bool Success { get; set; }

        /// <summary>
        /// Gets or sets the exception message if the update is not successful.
        /// </summary>
        public string ExceptionMessage { get; set; }

        /// <summary>
        /// Gets or sets the configuration update results.
        /// </summary>
        public IReadOnlyList<IConfigurationUpdateResult> ConfigurationUpdateResults { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EtwTraceLevel.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Enumeration with the ETW event levels.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System.Diagnostics.CodeAnalysis;

    // ReSharper disable UnusedMember.Global

    /// <summary>
    /// Enumerates the ETW trace levels used by most providers.
    /// </summary>
    [SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "Needs to have the same size as the native equivalent")]
    internal enum EtwTraceLevel : byte
    {
        /// <summary>
        /// Always log.
        /// </summary>
        LogAlways = 0x0,

        /// <summary>
        /// Critical logging only.
        /// </summary>
        Critical = 0x1,

        /// <summary>
        /// Logging errors.
        /// </summary>
        Error = 0x2,

        /// <summary>
        /// Logging warnings.
        /// </summary>
        Warning = 0x3,

        /// <summary>
        /// Informational logging.
        /// </summary>
        Informational = 0x4,

        /// <summary>
        /// Verbose logging.
        /// </summary>
        Verbose = 0x5
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RawListener.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Wraps the win32 calls to create an ETW listener that allows that client to receive the buffer and event callbacks.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;

    /// <summary>
    /// Type that wraps the win32 calls to create an ETW listener that allows that client to receive
    /// the raw buffer and event callbacks when processing a trace (real-time or file).
    /// </summary>
    internal unsafe sealed class RawListener : IDisposable
    {
        /// <summary>
        /// The .Net 3.5 does not have Environment.Is64BitProcess use the sizeof(IntPtr) as a hack
        /// to figure this out.
        /// </summary>
        private static readonly bool Is64BitProcess = sizeof(IntPtr) == 8;

        /// <summary>
        /// Win32 opaque trace handles passed to the APIs to refer to the trace sessions being listened.
        /// </summary>
        /// <remarks>
        /// Create as an array because it is the way that it is passed to the OpenTrace API.
        /// </remarks>
        private readonly ulong[] traceHandles;

        /// <summary>
        /// Keeps the trace logs structures on memory while processing the traces.
        /// </summary>
        /// <remarks>
        /// Need to suppress ReSharper here because this struct ensures that the ETW callbacks
        /// are kept alive.
        /// </remarks>
        // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
        private readonly NativeMethods.EventTraceLogfilew[] traceLogs;

        /// <summary>
        /// Initializes a new instance of the <see cref="RawListener"/> class.
        /// </summary>
        /// <param name="sessionOrEtlFiles">
        /// An enumerable with a single item if listen to an ETW real-time session or a sequence of ETL file names
        /// that are going to be processed together.
        /// </param>
        /// <param name="eventRecordCallback">
        /// The event record callback provided by the client. This can be null if the
        /// eventBufferCallback parameter is not.
        /// </param>
        /// <param name="eventBufferCallback">
        /// The event buffer callback provided by the client. This can be null if the
        /// eventRecordCallback parameter is not.
        /// </param>
        /// <param name="isFileTrace">
        /// Boolean to indicate whether this is a file or real-time ETW listener.
        /// </param>
        /// <param name="useRawTimestamps">
        /// True to indicate that the timestamps of events will be raw or false if ETW should convert them to FILETIME.
        /// </param>
        private RawListener(
            IEnumerable<string> sessionOrEtlFiles,
            NativeMethods.EventRecordCallback eventRecordCallback,
            NativeMethods.EventTraceBufferCallback eventBufferCallback,
            bool isFileTrace,
            bool useRawTimestamps = true)
        {
            if (sessionOrEtlFiles == null)
            {
                throw new ArgumentNullException("sessionOrEtlFiles");
            }

            var tracesEnum = from trace in sessionOrEtlFiles
                             where !string.IsNullOrEmpty(trace)
                             select trace;
            var traces = tracesEnum.ToList();
            if (traces.Count < 1)
            {
                throw new ArgumentException(
                    "At least one non-null, non-empty session or etl file name must be provided");
            }

            if (eventRecordCallback == null && eventBufferCallback == null)
            {
                throw new ArgumentException("At least one of the callbacks must be specified");
            }

            var traceIndex = 0;
            this.traceHandles = new ulong[traces.Count];
            this.traceLogs = new NativeMethods.EventTraceLogfilew[traces.Count];
            foreach (var traceName in traces)
            {
                this.traceLogs[traceIndex].EventCallback = eventRecordCallback;
                this.traceLogs[traceIndex].BufferCallback = eventBufferCallback;
                this.traceLogs[traceIndex].LogFileMode = NativeMethods.ProcessTraceModeEventRecord
                    | (useRawTimestamps ? NativeMethods.ProcessTraceModeRawTimestamp : 0);

                if (isFileTrace)
                {
                    this.traceLogs[traceIndex].LogFileName = traceName;
                }
                else
                {
                    this.traceLogs[traceIndex].LoggerName = traceName;
                    this.traceLogs[traceIndex].LogFileMode |= NativeMethods.ProcessTraceModeRealTime;
                }

                // New ETW session mode for Windows 8.1, Server 2012 R2, and later that avoids
                // slow consumers on one session to affect other sessions. Adding by default to all
                // sessions since it does not have adverse impact and it is just ignored by legacy OSes.
                this.traceLogs[traceIndex].LogFileMode |= NativeMethods.EventTraceIndependentSessionMode;

                var traceHandle = NativeMethods.OpenTrace(ref this.traceLogs[traceIndex]);
                if ((!Is64BitProcess && traceHandle == NativeMethods.InvalidTracehandle32) ||
                     (Is64BitProcess && traceHandle == NativeMethods.InvalidTracehandle64))
                {
                    throw new Win32Exception(string.Format(
                        CultureInfo.InvariantCulture,
                        "OpenTrace call for trace '{0}' failed.",
                        traceName));
                }

                this.traceHandles[traceIndex++] = traceHandle;
            }
        }

        /// <summary>
        /// Creates a new instance of the <see cref="RawListener"/> class to listen to an
        /// ETW real-time session.
        /// </summary>
        /// <param name="sessionName">
        /// Name of the ETW real-time session from which the client wants to listen for events.
        /// </param>
        /// <param name="eventRecordCallback">
        /// The event record callback provided by the client. This can be null if the
        /// eventBufferCallback parameter is not.
        /// </param>
        /// <param name="eventBufferCallback">
        /// The event buffer callback provided by the client. This can be null if the
        /// eventRecordCallback parameter is not.
        /// </param>
        /// <param name="useRawTimestamps">
        /// True to indicate that the timestamps of events will be raw or false if ETW should convert them to FILETIME.
        /// </param>
        /// <returns>
        /// The new instance of the <see cref="RawListener"/> class to listen to an
        /// ETW real-time session.
        /// </returns>
        public static RawListener CreateRealTimeListener(
            string sessionName,
            NativeMethods.EventRecordCallback eventRecordCallback,
            NativeMethods.EventTraceBufferCallback eventBufferCallback,
            bool useRawTimestamps = true)
        {
            if (string.IsNullOrEmpty(sessionName))
            {
                throw new ArgumentException("Session name cannot be null or empty", "sessionName");
            }

            return new RawListener(new[] { sessionName }, eventRecordCallback, eventBufferCallback, false, useRawTimestamps);
        }

        /// <summary>
        /// Creates a new instance of the <see cref="RawListener"/> class to process a set of ETL files.
        /// </summary>
        /// <param name="etlFiles">
        /// Sequence with the names of the ETL files from which the client wants to listen for events.
        /// </param>
        /// <param name="eventRecordCallback">
        /// The event record callback provided by the client. This can be null if the
        /// eventBufferCallback parameter is not.
        /// </param>
        /// <param name="eventBufferCallback">
        /// The event buffer callback provided by the client. This can be null if the
        /// eventRecordCallback parameter is not.
        /// </param>
        /// <param name="useRawTimestamps">
        /// True to indicate that the timestamps of events will be raw or false if ETW should convert them to FILETIME.
        /// </param>
        /// <returns>
        /// The new instance of the <see cref="RawListener"/> class to process a set of ETL files.
        /// </returns>
        public static RawListener CreateEtlFileListener(
            IEnumerable<string> etlFiles,
            NativeMethods.EventRecordCallback eventRecordCallback,
            NativeMethods.EventTraceBufferCallback eventBufferCallback,
            bool useRawTimestamps = true)
        {
            if (etlFiles == null)
            {
                throw new ArgumentNullException("etlFiles");
            }

            var nonEmptyEtlsEnum = from etl in etlFiles
                             where !string.IsNullOrEmpty(etl)
                             select etl;
            var nonEmptyEtls = nonEmptyEtlsEnum.ToList();
            if (nonEmptyEtls.Count < 1)
            {
                throw new ArgumentException(
                    "At least one non-null and non-empty etl file name must be provided");
            }

            return new RawListener(nonEmptyEtls, eventRecordCallback, eventBufferCallback, true, useRawTimestamps);
        }

        /// <summary>
        /// Starts to listen for the events, this will trigger the callbacks passed on the
        /// constructor to be called.
        /// </summary>
        public void Process()
        {
            var error = NativeMethods.ProcessTrace(
                this.traceHandles,
                (uint)this.traceHandles.Length,
                IntPtr.Zero,
                IntPtr.Zero);
            if (error != NativeMethods.ErrorSuccess)
            {
                throw new Win32Exception(error);
            }
        }

        /// <summary>
        /// Disposes the associated native resources.
        /// </summary>
        public void Dispose()
        {
            foreach (var handle in this.traceHandles)
            {
                NativeMethods.CloseTrace(handle);
            }
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="SerializationUtils.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// Set of functions used for metrics serialization and deserialization.
    /// </summary>
    public static class SerializationUtils
    {
        /// <summary>
        /// A string which replaces empty dimension value.
        /// </summary>
        public const string EmptyDimensionValueString = "__Empty";

        /// <summary>
        /// One minute size in 100th nanoseconds.
        /// </summary>
        public const long OneMinuteInterval = 600000000;

        /// <summary>
        /// Writes UInt16 value to the stream.
        /// </summary>
        /// <param name="stream"><see cref="Stream"/> to write to.</param>
        /// <param name="value">Value to write.</param>
        public static void Write(Stream stream, ushort value)
        {
            stream.WriteByte((byte)(value & 0xFF));
            stream.WriteByte((byte)((value & 0xFF00) >> 8));
        }

        /// <summary>
        /// Writes int value Base-128 encoded.
        /// </summary>
        /// <param name="writer">Binary writer to be used.</param>
        /// <param name="value">Value to write.</param>
        public static void WriteInt32AsBase128(BinaryWriter writer, int value)
        {
            WriteInt64AsBase128(writer, value);
        }

        /// <summary>
        /// Writes long value Base-128 encoded.
        /// </summary>
        /// <param name="writer">Binary writer to be used.</param>
        /// <param name="value">Value to write.</param>
        public static void WriteInt64AsBase128(BinaryWriter writer, long value)
        {
            var negative = value < 0;
            var t = negative ? -value : value;
            var first = true;
            do
            {
                byte b;
                if (first)
                {
                    b = (byte)(t & 0x3f);
                    t >>= 6;
                    if (negative)
                    {
                        b = (byte)(b | 0x40);
                    }

                    first = false;
                }
                else
                {
                    b = (byte)(t & 0x7f);
                    t >>= 7;
                }

                if (t > 0)
                {
                    b |= 0x80;
                }

                writer.Write(b);
            }
            while (t > 0);
        }

        /// <summary>
        /// Writes uint value Base-128 encoded.
        /// </summary>
        /// <param name="writer">Binary writer to be used.</param>
        /// <param name="value">Value to write.</param>
        public static void WriteUInt32AsBase128(BinaryWriter writer, uint value)
        {
            WriteUInt64AsBase128(writer, value);
        }

        /// <summary>
        /// Writes uint value as 4 bytes, but Base-128 encoded.
        /// Used for back compatibility between some versions of serializer.
        /// </summary>
        /// <param name="writer">Binary writer to be used.</param>
        /// <param name="value">Value to write.</param>
        public static void WriteUInt32InBase128AsFixed4Bytes(BinaryWriter writer, uint value)
        {
            var count = 0;
            ulong t = value;
            do
            {
                var b = (byte)(t & 0x7f);
                t >>= 7;
                ++count;
                if (t > 0 || count < 4)
                {
                    b |= 0x80;
                }

                writer.Write(b);
            }
            while (t > 0);

            for (; ++count <= 4;)
            {
                writer.Write(count == 4 ? (byte)0x0 : (byte)0x80);
            }
        }

        /// <summary>
        /// Writes ulong value Base-128 encoded.
        /// </summary>
        /// <param name="writer">Binary writer to be used.</param>
        /// <param name="value">Value to write.</param>
        public static void WriteUInt64AsBase128(BinaryWriter writer, ulong value)
        {
            var t = value;
            do
            {
                var b = (byte)(t & 0x7f);
                t >>= 7;
                if (t > 0)
                {
                    b |= 0x80;
                }

                writer.Write(b);
            }
            while (t > 0);
        }

        /// <summary>
        /// Writes ulong value Base-128 encoded to the buffer starting from the specified offset.
        /// </summary>
        /// <param name="buffer">Buffer used for writing.</param>
        /// <param name="offset">Offset to start with. Will be moved to the next byte after written.</param>
        /// <param name="value">Value to write.</param>
        public static void WriteUInt64AsBase128(byte[] buffer, ref int offset, ulong value)
        {
            var t = value;
            do
            {
                var b = (byte)(t & 0x7f);
                t >>= 7;
                if (t > 0)
                {
                    b |= 0x80;
                }

                buffer[offset++] = b;
            }
            while (t > 0);
        }

        /// <summary>
        /// Writes a histogram.
        /// </summary>
        /// <param name="writer">Binary writer to be used.</param>
        /// <param name="bucketsCount">The number of buckets in histogram (must be equal to the number of elements in buckets collection).</param>
        /// <param name="buckets">Histogram buckets to write.</param>
        /// <param name="hasHistogramSizePrefix">A flag indicating whether histogram size (in bytes)
        ///     should be put as a 4-bytes prefix in the beginning of the histogram data.</param>
        public static void WriteHistogramDataHistogram(BinaryWriter writer, int bucketsCount, IEnumerable<KeyValuePair<ulong, uint>> buckets, bool hasHistogramSizePrefix = false)
        {
            var startPosition = writer.BaseStream.Position;
            if (hasHistogramSizePrefix)
            {
                writer.Write(0);
            }

            WriteUInt32AsBase128(writer, (uint)bucketsCount);

            int bucketsWritten = 0;
            ulong prevKey = 0;
            uint prevValue = 0;
            var firstTime = true;
            foreach (var bucket in buckets)
            {
                if (firstTime)
                {
                    prevKey = bucket.Key;
                    prevValue = bucket.Value;
                    WriteUInt64AsBase128(writer, prevKey);
                    WriteUInt32AsBase128(writer, prevValue);
                    firstTime = false;
                }
                else
                {
                    WriteUInt64AsBase128(writer, bucket.Key - prevKey);
                    WriteInt32AsBase128(writer, (int)bucket.Value - (int)prevValue);
                    prevKey = bucket.Key;
                    prevValue = bucket.Value;
                }

                ++bucketsWritten;
            }

            if (bucketsCount != bucketsWritten)
            {
                throw new ArgumentException(
                    $"The actual number of buckets in the {nameof(buckets)} was {bucketsWritten}, the passed {nameof(bucketsCount)} is {bucketsCount}",
                    nameof(bucketsCount));
            }

            if (hasHistogramSizePrefix)
            {
                var endPosition = writer.BaseStream.Position;
                writer.BaseStream.Position = startPosition;
                writer.Write((int)(endPosition - startPosition - sizeof(int)));
                writer.BaseStream.Position = endPosition;
            }
        }

        /// <summary>
        /// Writes collection of <see cref="HyperLogLogSketch"/> objects to a <see cref="BinaryWriter"/>.
        /// </summary>
        /// <param name="writer"><see cref="BinaryWriter"/> object used for writing.</param>
        /// <param name="count">Number of sketches to write.</param>
        /// <param name="data">Collection of sketch dimension - sketch value pairs.</param>
        public static void WriteHyperLogLogSketches(BinaryWriter writer, uint count, IEnumerable<KeyValuePair<string, HyperLogLogSketch>> data)
        {
            var startPosition = writer.BaseStream.Position;
            writer.Write(0);

            WriteUInt32AsBase128(writer, count);

            foreach (var sketch in data)
            {
                writer.Write(sketch.Key);
                writer.Write(sketch.Value.BValue);
                writer.Write(sketch.Value.Registers, 0, sketch.Value.Registers.Length);
            }

            var endPosition = writer.BaseStream.Position;
            writer.BaseStream.Position = startPosition;
            writer.Write((int)(endPosition - startPosition - sizeof(int)));
            writer.BaseStream.Position = endPosition;
        }

        /// <summary>
        /// Writes collection of <see cref="HyperLogLogSketch"/> objects to a <see cref="BinaryWriter"/>.
        /// </summary>
        /// <param name="writer"><see cref="BinaryWriter"/> object used for writing.</param>
        /// <param name="count">Number of sketches to write.</param>
        /// <param name="data">Collection of sketch dimension - sketch value pairs.</param>
        /// <remarks>
        /// Will be replaced by V1 once deserialization code reaches everywhere.
        ///
        /// HLL data is serialized in two format:
        /// Sparse HLL data format
        ///     Version = 2 as byte
        ///     BValue as byte
        ///     For all non-zero elements :
        ///         Position In Sketch(ushort), Value at that position(byte)
        /// Full HLL data format
        ///     Version = 1 as byte
        ///     BValue as byte
        ///     For all elements :
        ///         Value at that position(byte)
        /// </remarks>
        public static void WriteHyperLogLogSketchesV2(BinaryWriter writer, uint count, IEnumerable<KeyValuePair<string, HyperLogLogSketch>> data)
        {
            var startPosition = writer.BaseStream.Position;
            writer.Write(0);

            WriteUInt32AsBase128(writer, count);

            foreach (var sketch in data)
            {
                writer.Write(sketch.Key);

                ushort nonZeroElements = 0;
                for (int i = 0; i < sketch.Value.Registers.Length; i++)
                {
                    if (sketch.Value.Registers[i] > 0)
                    {
                        nonZeroElements++;
                    }
                }

                // Position is 2 bytes, value is 1 byte, length of nonzero bytes is 2 bytes
                if ((nonZeroElements * 2) + nonZeroElements + 2 < sketch.Value.Registers.Length)
                {
                    // Sparse HLL
                    writer.Write((byte)2);
                    writer.Write(sketch.Value.BValue);
                    writer.Write(nonZeroElements);
                    for (ushort i = 0; i < sketch.Value.Registers.Length; i++)
                    {
                        if (sketch.Value.Registers[i] > 0)
                        {
                            writer.Write(i);
                            writer.Write(sketch.Value.Registers[i]);
                        }
                    }
                }
                else
                {
                    // Non Sparse HLL
                    writer.Write((byte)1);
                    writer.Write(sketch.Value.BValue);
                    writer.Write(sketch.Value.Registers, 0, sketch.Value.Registers.Length);
                }
            }

            var endPosition = writer.BaseStream.Position;
            writer.BaseStream.Position = startPosition;
            writer.Write((int)(endPosition - startPosition - sizeof(int)));
            writer.BaseStream.Position = endPosition;
        }

        /// <summary>
        /// Estimates a size in bytes of the uint value when written in Base-128.
        /// </summary>
        /// <param name="value">Value which size has to be estimated.</param>
        /// <returns>The estimated size in bytes.</returns>
        public static long EstimateUInt32InBase128Size(uint value)
        {
            return value <= 0x7F ? 1 : (value < 0x91011 ? 2 : (value < 0x1FFFFF ? 3 : 4));
        }

        /// <summary>
        /// Reads int value stored in Base-128 encoding.
        /// </summary>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <returns>Read value.</returns>
        public static int ReadInt32FromBase128(BinaryReader reader)
        {
            return (int)ReadInt64FromBase128(reader);
        }

        /// <summary>
        /// Reads long value stored in Base-128 encoding.
        /// </summary>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <returns>Read value.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static long ReadInt64FromBase128(BinaryReader reader)
        {
            int dummy = 0;
            return ReadInt64FromBase128(reader, ref dummy);
        }

        /// <summary>
        /// Reads long value stored in Base-128 encoding.
        /// </summary>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <param name="bytesRead">The number that is incremented each time a single byte is read.</param>
        /// <returns>Read value.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static long ReadInt64FromBase128(BinaryReader reader, ref int bytesRead)
        {
            long val = 0;
            var shift = 0;
            byte b;
            var first = true;
            var negative = false;
            do
            {
                if (first)
                {
                    first = false;
                    b = reader.ReadByte();
                    bytesRead++;
                    val += (b & 0x3f) << shift;
                    negative = (b & 0x40) != 0;
                    shift += 6;
                }
                else
                {
                    b = reader.ReadByte();
                    bytesRead++;
                    val += (long)(b & 0x7f) << shift;
                    shift += 7;
                }
            }
            while ((b & 0x80) != 0);
            return negative ? -val : val;
        }

        /// <summary>
        /// Reads uint value stored in Base-128 encoding.
        /// </summary>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <returns>Read value.</returns>
        public static uint ReadUInt32FromBase128(BinaryReader reader)
        {
            return (uint)ReadUInt64FromBase128(reader);
        }

        /// <summary>
        /// Reads uint value stored in Base-128 encoding.
        /// </summary>
        /// <param name="buffer">Buffer from which value to be read.</param>
        /// <param name="offset">Offset in buffer to start reading from.</param>
        /// <returns>Read value.</returns>
        public static uint ReadUInt32FromBase128(byte[] buffer, ref int offset)
        {
            return (uint)ReadUInt64FromBase128(buffer, ref offset);
        }

        /// <summary>
        /// Reads ulong value stored in Base-128 encoding.
        /// </summary>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <returns>Read value.</returns>
        public static ulong ReadUInt64FromBase128(BinaryReader reader)
        {
            ulong val = 0;
            var shift = 0;
            byte b;
            do
            {
                b = reader.ReadByte();
                val = val + ((ulong)(b & 0x7f) << shift);
                shift += 7;
            }
            while ((b & 0x80) != 0);
            return val;
        }

        /// <summary>
        /// Reads ulong value stored in Base-128 encoding.
        /// </summary>
        /// <param name="buffer">Buffer from which value to be read.</param>
        /// <param name="offset">Offset in buffer to start reading from.</param>
        /// <returns>Read value.</returns>
        public static ulong ReadUInt64FromBase128(byte[] buffer, ref int offset)
        {
            ulong val = 0;
            var shift = 0;
            byte b;
            do
            {
                b = buffer[offset++];
                val = val + ((ulong)(b & 0x7f) << shift);
                shift += 7;
            }
            while ((b & 0x80) != 0);
            return val;
        }

        /// <summary>
        /// Reads a histogram.
        /// </summary>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <param name="hasHistogramSizePrefix">A flag indicating whether histogram
        /// data contains histogram size (in bytes) as a 4-bytes prefix.</param>
        /// <returns>An enumerable of key values pairs where key is a sample value
        /// and value is a number of times the sample value appeared.</returns>
        public static IEnumerable<KeyValuePair<ulong, uint>> ReadHistogram(BinaryReader reader, bool hasHistogramSizePrefix = false)
        {
            if (hasHistogramSizePrefix)
            {
                // Not used now, but left for optimization later
                reader.ReadInt32();
            }

            var size = ReadUInt32FromBase128(reader);
            ulong prevKey = 0;
            uint prevValue = 0;
            for (var i = 0; i < size; ++i)
            {
                if (i == 0)
                {
                    prevKey = ReadUInt64FromBase128(reader);
                    prevValue = ReadUInt32FromBase128(reader);
                }
                else
                {
                    prevKey += ReadUInt64FromBase128(reader);
                    prevValue = (uint)(prevValue + ReadInt32FromBase128(reader));
                }

                yield return new KeyValuePair<ulong, uint>(prevKey, prevValue);
            }
        }

        /// <summary>
        /// Reads a histogram to a list.
        /// </summary>
        /// <param name="list">List to append to</param>
        /// <param name="reader">Binary reader to be used for reading.</param>
        /// <param name="hasHistogramSizePrefix">A flag indicating whether histogram
        /// data contains histogram size (in bytes) as a 4-bytes prefix.</param>
        public static void ReadHistogramTo(List<KeyValuePair<ulong, uint>> list, BinaryReader reader, bool hasHistogramSizePrefix = false)
        {
            list.Clear();

            if (hasHistogramSizePrefix)
            {
                // Not used now, but left for optimization later
                reader.ReadInt32();
            }

            var size = ReadUInt32FromBase128(reader);
            ulong prevKey = 0;
            uint prevValue = 0;

            list.EnsureSpace((int)size, geometryGrowth: true);

            for (var i = 0; i < size; ++i)
            {
                if (i == 0)
                {
                    prevKey = ReadUInt64FromBase128(reader);
                    prevValue = ReadUInt32FromBase128(reader);
                }
                else
                {
                    prevKey += ReadUInt64FromBase128(reader);
                    prevValue = (uint)(prevValue + ReadInt32FromBase128(reader));
                }

                list.Add(new KeyValuePair<ulong, uint>(prevKey, prevValue));
            }
        }

        /// <summary>
        /// Assigns the hyperloglog sketches.
        /// </summary>
        /// <param name="reader">Stream reader containing the sketches data.</param>
        /// <param name="length">Length of data to read.</param>
        /// <param name="sketchConstructor">Function used to construct <see cref="HyperLogLogSketch"/> object.</param>
        /// <returns>An <see cref="IEnumerable{T}"/> of dimension name and hyperloglog sketch key value pair.</returns>
        public static IEnumerable<KeyValuePair<string, HyperLogLogSketch>> ReadHyperLogLogSketch(BinaryReader reader, int length, Func<int, HyperLogLogSketch> sketchConstructor)
        {
            var size = ReadUInt32FromBase128(reader);
            for (var i = 0; i < size; ++i)
            {
                var dimensionName = reader.ReadString();
                short bValue;
                var sketch = ReadHyperLogLogSketch(reader, sketchConstructor, out bValue);
                yield return new KeyValuePair<string, HyperLogLogSketch>(dimensionName, sketch);
            }
        }

        /// <summary>
        /// Reads the hyperloglog sketch.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="sketchConstructor">The sketch constructor.</param>
        /// <param name="bValue">B value.</param>
        /// <returns>Deserialized sketch.</returns>
        /// <remarks>
        /// HLL data is serialized in three formats:
        /// Legacy HLL data format
        ///     BValue as byte
        ///     For all elements :
        ///         Value at that position(byte)
        /// Sparse HLL data format
        ///     Version = 2 as byte
        ///     BValue as byte
        ///     For all non-zero elements :
        ///         Position In Sketch(ushort), Value at that position(byte)
        /// Full HLL data format
        ///     Version = 1 as byte
        ///     BValue as byte
        ///     For all elements :
        ///         Value at that position(byte)
        /// </remarks>
        public static HyperLogLogSketch ReadHyperLogLogSketch(BinaryReader reader, Func<int, HyperLogLogSketch> sketchConstructor, out short bValue)
        {
            var version = (short)reader.ReadByte();
            if (version > 3)
            {
                // Legacy format when no version was present
                bValue = version;
                version = 0;
            }
            else
            {
                // All other formats has bValue as next element
                bValue = reader.ReadByte();
            }

            var sketch = sketchConstructor(bValue);

            if (version <= 1)
            {
                // Non-Sparse HLL
                var registersSize = 1 << bValue;
                for (var j = 0; j < registersSize; j++)
                {
                    sketch[j] = reader.ReadByte();
                }
            }
            else if (version == 2)
            {
                // Sparse HLL, hence format is position of nonzero items,value of nonzeroitem
                var lengthOfNonZeroElements = reader.ReadUInt16();
                for (var pos = 0; pos < lengthOfNonZeroElements; pos++)
                {
                    var itemPosition = reader.ReadUInt16();
                    sketch[itemPosition] = reader.ReadByte();
                }
            }

            return sketch;
        }

        /// <summary>
        /// Reads data from source stream and writes it to destination stream.
        /// </summary>
        /// <param name="source">Source stream.</param>
        /// <param name="destination">Destination stream.</param>
        /// <param name="len">Length to copy.</param>
        /// <param name="tempBuffer">Temp buffer.</param>
        public static void ReadFromStream(Stream source, Stream destination, int len, byte[] tempBuffer)
        {
            var readBytes = 0;
            while (readBytes < len)
            {
                var bytesToRead = Math.Min(tempBuffer.Length, len - readBytes);
                source.Read(tempBuffer, 0, bytesToRead);
                destination.Write(tempBuffer, 0, bytesToRead);
                readBytes += bytesToRead;
            }
        }

        /// <summary>
        /// Ensure List has enough capacity for new addition to reduce resizing
        /// </summary>
        /// <typeparam name="T">Generic type</typeparam>
        /// <param name="list">Existing list</param>
        /// <param name="addCount">Estimated number of items to add</param>
        /// <param name="geometryGrowth">True for geometry growth (50% more, O(N) algorithm),
        /// False for no extra capacity (could lead to O(N^2) algorithm, use when memory is critical and this is not called often.).</param>
        private static void EnsureSpace<T>(this List<T> list, int addCount, bool geometryGrowth = true)
        {
            if (addCount > 0)
            {
                int cap = list.Capacity;

                if (cap == 0)
                {
                    // New list, use toAdd
                    cap = addCount;
                }
                else
                {
                    int newCount = list.Count + addCount;

                    // List has enough capacity
                    if (newCount <= cap)
                    {
                        return;
                    }

                    if (geometryGrowth)
                    {
                        // Geometry growth (add 50%)
                        cap = Math.Max(newCount, cap * 3 / 2);
                    }
                    else
                    {
                        cap = newCount;
                    }
                }

                list.Capacity = cap;
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QueryLanguageResponseToDatatable.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// <summary>
    /// Class which helps in converting query language response from FE to Datatable.
    /// </summary>
    internal static class QueryLanguageResponseToDatatable
    {
        /// <summary>
        /// Converts the query language response to datatable.
        /// Input looks like as follows :
        /// {
        ///  "queryRequest": {
        ///    "metricIdentifier": {
        ///      "monitoringAccount": "MetricTeamInternalMetrics",
        ///      "metricNamespace": "Metrics.Server",
        ///      "metricName": "ClientAggregatedMetricCount"
        ///    },
        ///    "startTimeUtc": "0001-01-01T00:00:00.0000000Z",
        ///    "endTimeUtc": "0001-01-01T00:00:00.0000000Z",
        ///    "seriesResolutionInMinutes": 0,
        ///    "aggregationType": 0,
        ///    "numberOfResultsToReturn": 0,
        ///    "orderBy": 0,
        ///    "zeroAsNoValueSentinel": false,
        ///    "aggregateAcrossAccounts": false,
        ///    "lastValueMode": false
        ///  },
        ///  "endTimeUtc": "2019-01-29T06:48:00.0000000Z",
        ///  "startTimeUtc": "2019-01-29T05:48:00.0000000Z",
        ///  "timeResolutionInMinutes": 1,
        ///  "filteredTimeSeriesList": {
        ///    "$type": "Microsoft.Cloud.Metrics.Client.Query.FilteredTimeSeries[], Microsoft.Cloud.Metrics.Client",
        ///    "$values": [
        ///      {
        ///        "metricIdentifier": {
        ///          "monitoringAccount": "MetricTeamInternalMetrics",
        ///          "metricNamespace": "Metrics.Server",
        ///          "metricName": "ClientAggregatedMetricCount"
        ///        },
        ///        "dimensionList": {
        ///          "$type": "System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib],[System.String, mscorlib]][], mscorlib",
        ///          "$values": [{"key":"datacenter","value":"mdmtest1-black"}]
        /// },
        ///        "evaluatedResult": "NaN",
        ///        "timeSeriesValues": {
        ///          "$type": "System.Collections.Generic.KeyValuePair`2[[Microsoft.Cloud.Metrics.Client.Metrics.SamplingType, Microsoft.Cloud.Metrics.Client],[System.Double[], mscorlib]][], mscorlib",
        ///          "$values": [
        ///            {
        ///              "key": {
        ///                "name": "Average"
        ///              },
        ///              "value": [
        ///                265.10236098167132,
        ///              ]
        ///            }
        ///          ]
        ///        }
        ///      }
        ///    ]
        ///  },
        ///  "errorCode": 0,
        ///  "diagnosticInfo": {}
        /// }
        ///
        /// Output looks like as follows:
        /// [
        ///   {
        ///         "TimestampUtc": "02/04/2019 07:11:00",
        ///         "AccountName": "MetricTeamInternalMetrics",
        ///         "MetricNamespace": "Metrics.Server",
        ///         "MetricName": "ClientAggregatedMetricCount",
        ///         "Datacenter": "EastUS2",
        ///         "Average": 70.083851254134714
        ///    },
        ///    {
        ///         "TimestampUtc": "02/04/2019 07:12:00",
        ///         "AccountName": "MetricTeamInternalMetrics",
        ///         "MetricNamespace": "Metrics.Server",
        ///         "MetricName": "ClientAggregatedMetricCount",
        ///         "Datacenter": "EastUS2",
        ///         "Average": 67.305346411549351
        ///     }
        /// ]
        /// </summary>
        /// <param name="responseFromMetrics">Input data stream to convert to datatable.</param>
        /// <returns>
        /// Input data stream converted to datatable.
        /// </returns>
        public static JArray GetResponseAsTable(Stream responseFromMetrics)
        {
            using (var reader = new StreamReader(responseFromMetrics, Encoding.UTF8))
            {
                var jObject = (JObject)JsonSerializer.Create().Deserialize(reader, typeof(JObject));

                JArray returnObject = JArray.Parse("[]");
                var startTimeUtc = DateTime.Parse(jObject["startTimeUtc"].Value<string>());
                var timeResolution = TimeSpan.FromMinutes(jObject["timeResolutionInMinutes"].Value<int>());
                var outerValues = jObject["filteredTimeSeriesList"]["$values"] as JArray;
                Dictionary<DateTime, JObject> rowMap = new Dictionary<DateTime, JObject>();

                foreach (var outerValue in outerValues)
                {
                    var innerValues = outerValue["timeSeriesValues"]["$values"] as JArray;
                    string accountName = outerValue["metricIdentifier"]["monitoringAccount"].Value<string>();
                    string metricNamespace = outerValue["metricIdentifier"]["metricNamespace"].Value<string>();
                    string metricName = outerValue["metricIdentifier"]["metricName"].Value<string>();
                    var dimensions = outerValue["dimensionList"]["$values"] as JArray;

                    rowMap.Clear();

                    foreach (var actualValue in innerValues)
                    {
                        var samplingType = actualValue["key"]["name"].Value<string>();
                        var dataPoints = actualValue["value"] as JArray;

                        var currentTimeStamp = startTimeUtc;
                        foreach (var val in dataPoints)
                        {
                            JObject row;
                            if (rowMap.ContainsKey(currentTimeStamp))
                            {
                                row = rowMap[currentTimeStamp];
                            }
                            else
                            {
                                row = JObject.Parse("{}");
                                row.Add("TimestampUtc", currentTimeStamp.ToString(DateTimeFormatInfo.InvariantInfo));
                                row.Add("i_AccountName", accountName);
                                row.Add("i_MetricNamespace", metricNamespace);
                                row.Add("i_MetricName", metricName);

                                foreach (var dim in dimensions)
                                {
                                    row.Add(dim["key"].Value<string>(), dim["value"].Value<string>());
                                }

                                rowMap[currentTimeStamp] = row;
                                returnObject.Add(row);
                            }

                            var rowDimValue = val.Value<double>();
                            if (double.IsNaN(rowDimValue))
                            {
                                rowDimValue = 0;
                            }

                            row.Add(samplingType, rowDimValue);
                            currentTimeStamp = currentTimeStamp.Add(timeResolution);
                        }
                    }
                }

                return returnObject;
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricsServerRelativeUris.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    /// <summary>
    /// The metrics server relative URIs.
    /// </summary>
    public static class MetricsServerRelativeUris
    {
        /// <summary>
        /// The relative URL for metrics data
        /// </summary>
        public const string DataRelativeUrl = "v1/data/metrics";

        /// <summary>
        /// The relative URL for metrics meta-data a.k.a. hinting data
        /// </summary>
        public const string MetaDataRelativeUrl = "v1/hint";

        /// <summary>
        /// The relative URL for metrics meta-data a.k.a. hinting data V2.
        /// </summary>
        public const string MetaDataRelativeUrlV2 = "v2/hint";

        /// <summary>
        /// The relative URL for metrics configuration
        /// </summary>
        public const string ConfigRelativeUrl = "v1/config/metrics";

        /// <summary>
        /// The V2 relative URL for metrics configuration
        /// </summary>
        public const string ConfigRelativeUrlV2 = "v2/config/metrics";

        /// <summary>
        /// The relative URL for account configuration
        /// </summary>
        public const string TenantConfigRelativeUrl = "v1/config";

        /// <summary>
        /// The relative URL for health configuration.
        /// </summary>
        public const string HealthConfigRelativeUrl = "v2/config/health";

        /// <summary>
        /// The relative URL for monitoring account configuration
        /// </summary>
        public const string AccountConfigRelativeUrl = "v1/config/tenant";

        /// <summary>
        /// The relative URL for health controller
        /// </summary>
        public const string HealthRelativeUrl = "v3/data/health";

        /// <summary>
        /// The relative Url for distributed query.
        /// </summary>
        public const string DistributedQueryRelativeUrl = "flight/dq/batchedReadv3";

        /// <summary>
        /// The query service relative URL.
        /// </summary>
        public const string QueryServiceRelativeUrl = "query";
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ILocalAggregatedMetric.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// The interface representing the locally aggregated metric in the ETW stream.
    /// </summary>
    public interface ILocalAggregatedMetric
    {
        /// <summary>
        /// Gets the Monitoring Account to which this metric is reported.
        /// </summary>
        string MonitoringAccount { get; }

        /// <summary>
        /// Gets the metric namespace.
        /// </summary>
        string MetricNamespace { get; }

        /// <summary>
        /// Gets the metric name.
        /// </summary>
        string MetricName { get; }

        /// <summary>
        /// Gets the time in UTC when metric was reported.
        /// </summary>
        DateTime MetricTimeUtc { get; }

        /// <summary>
        /// Gets the dimension name-value dictionary.
        /// </summary>
        /// <remarks>The dimension names are case insensitive.</remarks>
        IReadOnlyDictionary<string, string> Dimensions { get; }

        /// <summary>
        /// Gets the scaling factor applied to metric values.
        /// </summary>
        float ScalingFactor { get; }

        /// <summary>
        /// Gets the number of samples for which this metric is reported.
        /// </summary>
        uint Count { get; }

        /// <summary>
        /// Gets the scaled sum of sample values reported this metric.
        /// </summary>
        float ScaledSum { get; }

        /// <summary>
        /// Gets the scaled minimum value of samples reported this metric.
        /// </summary>
        float ScaledMin { get; }

        /// <summary>
        /// Gets the scaled maximum value of samples reported this metric.
        /// </summary>
        float ScaledMax { get; }

        /// <summary>
        /// Gets the sum of sample values reported this metric.
        /// </summary>
        ulong Sum { get; }

        /// <summary>
        /// Gets the minimum value of samples reported this metric.
        /// </summary>
        ulong Min { get; }

        /// <summary>
        /// Gets the maximum value of samples reported this metric.
        /// </summary>
        ulong Max { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IEtlDispatcher.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
// Defines the dispatchers of ETL files being periodically generated.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System.Collections.Generic;

    /// <summary>
    /// Defines the dispatchers of ETL files being periodically generated.
    /// </summary>
    /// <remarks>
    /// The dispatcher will receive the ETL files to be dispatched to the ETL file subscribers of
    /// a specific ETW collection.
    /// </remarks>
    internal interface IEtlDispatcher
    {
        /// <summary>
        /// Receives an list with all available ETL files that could be submitted to
        /// the subscriber. The dispatcher should expect this list to be sorted from
        /// oldest to newest available ETL.
        /// </summary>
        /// <param name="availableEtlFiles">
        /// Enumeration with all the available ETL files. The dispatcher willl do some filtering
        /// before submitting then to the subscriber.
        /// </param>
        void EnqueueBacklogEtls(List<string> availableEtlFiles);

        /// <summary>
        /// Enqueues an ETL file for processing.
        /// </summary>
        /// <param name="etlFileName">
        /// The ETL file to be enqueued for processing.
        /// </param>
        void EnqueueEtlFile(string etlFileName);
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IReadOnlyTDigest.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System.IO;

    /// <summary>
    /// Read-only tDigest interface
    /// </summary>
    public interface IReadOnlyTDigest
    {
        /// <summary>
        /// Serialize a tdigest to a given writer
        /// </summary>
        /// <param name="writer">The writer to use</param>
        void Serialize(BinaryWriter writer);
    }
}
<file_sep>// --------------------------------------------------------------------------------------------------------------------
// <copyright file="KqlMRequest.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using Newtonsoft.Json;

    /// <summary>
    /// The query request used when the customer queries with a free text query in the KQL-M query language.
    /// </summary>
    internal sealed class KqlMRequest
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="KqlMRequest"/> class.
        /// Used for recreation with deserialization.
        /// </summary>
        public KqlMRequest()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="KqlMRequest"/> class.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metric">The metric name.</param>
        /// <param name="startTimeUtc">The start time of the query.</param>
        /// <param name="endTimeUtc">The end time of the query.</param>
        /// <param name="queryExpression">The query expression to execute.</param>
        [JsonConstructor]
        public KqlMRequest(
            string monitoringAccount,
            string metricNamespace,
            string metric,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            string queryExpression)
        {
            this.MonitoringAccount = monitoringAccount;
            this.MetricNamespace = metricNamespace;
            this.Metric = metric;
            this.StartTimeUtc = startTimeUtc;
            this.EndTimeUtc = endTimeUtc;
            this.QueryExpression = queryExpression;
        }

        /// <summary>
        /// The monitoring account in the context of the request.
        /// </summary>
        public string MonitoringAccount { get; private set; }

        /// <summary>
        /// The metric namespace in the context of the request.
        /// </summary>
        public string MetricNamespace { get; private set; }

        /// <summary>
        /// The name of the metric in the context of the request.
        /// </summary>
        public string Metric { get; private set; }

        /// <summary>
        /// The start time of the query in UTC time.
        /// </summary>
        public DateTime StartTimeUtc { get; private set; }

        /// <summary>
        /// The end time of the query in UTC time.
        /// </summary>
        public DateTime EndTimeUtc { get; private set; }

        /// <summary>
        /// The query expression to execute.
        /// </summary>
        public string QueryExpression { get; private set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SpecialCharsHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;

    /// <summary>
    /// Helper class to escape and un-escape special chars
    /// </summary>
    public class SpecialCharsHelper
    {
        /// <summary>
        /// Eescape the input string twice.
        /// </summary>
        /// <param name="input">The input string.</param>
        /// <returns>The escaped string</returns>
        public static string EscapeTwice(string input)
        {
            return Uri.EscapeDataString(Uri.EscapeDataString(input));
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Role.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// Determines what level of access an entity has to an MDM entity.
    /// </summary>
    public enum Role
    {
        /// <summary>
        /// For certificates only - can read metric data but cannot make any changes.
        /// </summary>
        ReadOnly,

        /// <summary>
        /// This role has the ability to modify and create dashboards within an account.
        /// </summary>
        DashboardEditor,

        /// <summary>
        /// This role has the ability to modify metric, monitor or health configuration within an account.
        /// </summary>
        ConfigurationEditor,

        /// <summary>
        /// For certificates only - can publish metrics but cannot make other changes.
        /// </summary>
        MetricPublisher,

        /// <summary>
        /// Full access to modify configuration, account settings and dashboards.
        /// </summary>
        Administrator,

        /// <summary>
        /// This role has the ability to modify and create monitors within an account.
        /// </summary>
        MonitorEditor,

        /// <summary>
        /// This role has the ability to modify and create monitors/metrics within an account.
        /// </summary>
        MetricAndMonitorEditor
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IDistinctCountConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;

    /// <summary>
    /// Configures distinct count feature on this preaggregate.
    /// </summary>
    public interface IDistinctCountConfiguration
    {
        /// <summary>
        /// Gets the dimensions.
        /// </summary>
        IEnumerable<string> Dimensions { get; }

        /// <summary>
        /// Adds the dimension.
        /// </summary>
        /// <param name="dimension">The dimension.</param>
        void AddDimension(string dimension);

        /// <summary>
        /// Removes the dimension.
        /// </summary>
        /// <param name="dimension">The dimension.</param>
        void RemoveDimension(string dimension);
    }
}<file_sep>﻿# About Metrics.MultiDimensionalMetricsClient and Metrics.Serialization

The two projects are copied from [EngSys-MDA-MetricsAndHealth](https://msazure.visualstudio.com/DefaultCollection/One/_git//EngSys-MDA-MetricsAndHealth).

We need the support from package `Microsoft.Cloud.Metrics.Client`, however, it was built againts .NET Framework and not compatible with .NET Core.
So we copied the relative source from the Metrics repo, and fixed the code so that it works in .NET Core.

The following are the list of modifications:

* Nuget dependencies for both project
* Metrics.MultiDimensionalMetricsClient
   * Utility/UserAccessTokenRefresher.cs
      * Line 81, `new PlatformParameters()` - removed constructor arguments
   * Utility/HttpClientHelper.cs
      * `WebRequestHandler` to `HttpClientHandler`
      * `WebRequestHandler.ServerCertificateValidationCallback` to `HttpClientHandler.ServerCertificateCustomValidationCallback`<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="CompositeMetricConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;
    using Online.Metrics.Serialization;

    /// <summary>
    /// Represents a composite metric in the MDM System.
    /// </summary>
    public sealed class CompositeMetricConfiguration : ICompositeMetricConfiguration
    {
        private readonly List<CompositeMetricSource> metricSources;
        private readonly List<CompositeExpression> compositeExpressions;

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositeMetricConfiguration"/> class.
        /// </summary>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="name">The name.</param>
        /// <param name="lastUpdatedTime">The last updated time.</param>
        /// <param name="lastUpdatedBy">The last updated by.</param>
        /// <param name="version">The version.</param>
        /// <param name="treatMissingSeriesAsZeroes">if set to <c>true</c> treat missing series as zeroes.</param>
        /// <param name="description">The description of the metric.</param>
        /// <param name="metricSources">The metric sources.</param>
        /// <param name="compositeExpressions">The composite expressions.</param>
        [JsonConstructor]
        internal CompositeMetricConfiguration(
            string metricNamespace,
            string name,
            DateTime lastUpdatedTime,
            string lastUpdatedBy,
            uint version,
            bool treatMissingSeriesAsZeroes,
            string description,
            IEnumerable<CompositeMetricSource> metricSources,
            IEnumerable<CompositeExpression> compositeExpressions)
        {
            this.MetricNamespace = metricNamespace;
            this.Name = name;
            this.LastUpdatedTime = lastUpdatedTime;
            this.LastUpdatedBy = lastUpdatedBy;
            this.Version = version;
            this.TreatMissingSeriesAsZeroes = treatMissingSeriesAsZeroes;
            this.Description = description;
            this.metricSources = metricSources.ToList();
            this.compositeExpressions = compositeExpressions.ToList();
        }

        /// <summary>
        /// The namespace of the metric.
        /// </summary>
        public string MetricNamespace { get; }

        /// <summary>
        /// The name of the metric.
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// The last updated time of the metric.
        /// </summary>
        public DateTime LastUpdatedTime { get; }

        /// <summary>
        /// The last entity to update the metric.
        /// </summary>
        public string LastUpdatedBy { get; }

        /// <summary>
        /// The version of the metric.
        /// </summary>
        public uint Version { get; }

        /// <summary>
        /// Gets the description of the metric.
        /// </summary>
        public string Description { get; }

        /// <summary>
        /// Gets the metric sources.
        /// </summary>
        public IEnumerable<CompositeMetricSource> MetricSources
        {
            get { return this.metricSources; }
        }

        /// <summary>
        /// Gets the composite expressions.
        /// </summary>
        public IEnumerable<CompositeExpression> CompositeExpressions
        {
            get { return this.compositeExpressions; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to treat missing series as zeroes.
        /// </summary>
        public bool TreatMissingSeriesAsZeroes { get; set; }

        /// <summary>
        /// Creates the composite metric configuration.
        /// </summary>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metric">The metric.</param>
        /// <param name="metricSources">The metric sources.</param>
        /// <param name="expressions">The expressions.</param>
        /// <param name="treatMissingSeriesAsZeroes">if set to <c>true</c> [treat missing series as zeroes].</param>
        /// <param name="description">The optional description of the metric.</param>
        /// <returns>The composite metric configuration.</returns>
        public static CompositeMetricConfiguration CreateCompositeMetricConfiguration(
            string metricNamespace,
            string metric,
            IEnumerable<CompositeMetricSource> metricSources,
            IEnumerable<CompositeExpression> expressions,
            bool treatMissingSeriesAsZeroes,
            string description = "")
        {
            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrWhiteSpace(metric))
            {
                throw new ArgumentNullException(nameof(metric));
            }

            if (metricSources == null)
            {
                throw new ArgumentNullException(nameof(metricSources));
            }

            if (expressions == null)
            {
                throw new ArgumentNullException(nameof(expressions));
            }

            if (description.Length > SerializationConstants.MaximumMetricDescriptionLength)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(description),
                    $"The metric description cannot be greater than {SerializationConstants.MaximumMetricDescriptionLength} characters.");
            }

            return new CompositeMetricConfiguration(metricNamespace, metric, DateTime.MinValue, string.Empty, 0, treatMissingSeriesAsZeroes, description, metricSources, expressions);
        }

        /// <summary>
        /// Adds the metric source.
        /// </summary>
        /// <param name="metricSource">The metric source.</param>
        public void AddMetricSource(CompositeMetricSource metricSource)
        {
            if (metricSource == null)
            {
                throw new ArgumentNullException(nameof(metricSource));
            }

            if (this.metricSources.Any(x => string.Equals(x.DisplayName, metricSource.DisplayName, StringComparison.OrdinalIgnoreCase)))
            {
                throw new ConfigurationValidationException("Cannot add metric sources with duplicate names.", ValidationType.DuplicateMetricSource);
            }

            this.metricSources.Add(metricSource);
        }

        /// <summary>
        /// Removes the metric source.
        /// </summary>
        /// <param name="metricSourceName">The metric source name.</param>
        public void RemoveMetricSource(string metricSourceName)
        {
            this.metricSources.RemoveAll(x => string.Equals(x.DisplayName, metricSourceName, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// Adds the expression.
        /// </summary>
        /// <param name="expression">The expression.</param>
        public void AddExpression(CompositeExpression expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

            if (this.compositeExpressions.Any(x => string.Equals(x.Name, expression.Name, StringComparison.OrdinalIgnoreCase)))
            {
                throw new ConfigurationValidationException("Cannot add composite expressions with duplicate names.", ValidationType.DuplicateSamplingType);
            }

            this.compositeExpressions.Add(expression);
        }

        /// <summary>
        /// Removes the expression.
        /// </summary>
        /// <param name="expressionName">The expression name.</param>
        public void RemoveExpression(string expressionName)
        {
            this.compositeExpressions.RemoveAll(x => string.Equals(x.Name, expressionName, StringComparison.OrdinalIgnoreCase));
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EtwSessionManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Types that encapsulate an ETW session.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime.InteropServices;

    /// <summary>
    /// Helper class that manages ETW sessions.
    /// </summary>
    internal static unsafe class EtwSessionManager
    {
        /// <summary>
        /// Maximum file name size.
        /// </summary>
        private const uint MaxNameSize = 1024;

        /// <summary>
        /// Size of the extension area where session and file name can be added to the end of NativeMethods.EventTraceProperties.
        /// </summary>
        /// <remarks>
        /// It accounts for 2 strings (which can be up to MaxNameSize in length).
        /// </remarks>
        private const uint ExtSize = 2 * MaxNameSize * sizeof(char);

        /// <summary>
        /// Maximum number of ETW sessions that can be handled by the QueryAllTraces API.
        /// </summary>
        private const uint MaxSessionsByQueryAllTraces = 64;

        /// <summary>
        /// Keeps the size of the managed version of the EventTraceProperties struct.
        /// </summary>
        private static readonly uint TracePropertiesSize;

        /// <summary>
        /// A shared buffer to be used by static methods to avoid allocation and de-allocation on every call.
        /// </summary>
        [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "Not accessible to any 3rd-party MS or not")]
        private static IntPtr sharedBuffer;

        /// <summary>
        /// A object to serve as a lock for the sharing of the sharedBuffer.
        /// </summary>
        private static object sharedBufferLock = new object();

        /// <summary>
        /// Keeps track of the size of the shared buffer.
        /// </summary>
        private static int sharedBufferSize;

        /// <summary>
        /// Keeps track of the number of expected active ETW sessions when querying all sessions.
        /// </summary>
        private static uint expectedActiveEtwSessions = 32;

        /// <summary>
        /// Initializes static members of the <see cref="EtwSessionManager"/> class.
        /// </summary>
        static EtwSessionManager()
        {
            TracePropertiesSize = (uint)Marshal.SizeOf(typeof(NativeMethods.EventTraceProperties));
            sharedBufferSize = (int)(TracePropertiesSize + ExtSize);
            sharedBuffer = Marshal.AllocHGlobal(sharedBufferSize);
        }

        /// <summary>
        /// Attempts to stop the specified session exists on the system.
        /// </summary>
        /// <param name="sessionName">
        /// Name of the session to be stopped.
        /// </param>
        /// <returns>
        /// True if the given session was stopped false otherwise.
        /// </returns>
        public static bool Stop(string sessionName)
        {
            const int EtwSessionNotFound = 4201;
            int result;
            ControlTrace(sessionName, NativeMethods.TraceControl.Stop, out result);

            return result == NativeMethods.ErrorSuccess || result == EtwSessionNotFound;
        }

        /// <summary>
        /// Attempts to retrieve the properties (including status) of the given ETW session.
        /// </summary>
        /// <param name="sessionName">
        /// The name for the session which the caller wants to retrieve the properties.
        /// </param>
        /// <param name="traceProperties">
        /// The <see cref="NativeMethods.EventTraceProperties"/> instance describing the session passed
        /// by the caller. It contains valid data only if the method returns true;.
        /// </param>
        /// <returns>
        /// True if the function retrieved the session properties, false otherwise.
        /// </returns>
        public static bool TryGetSessionProperties(string sessionName, out NativeMethods.EventTraceProperties traceProperties)
        {
            int result;
            traceProperties = ControlTrace(sessionName, NativeMethods.TraceControl.Query, out result);

            return result == NativeMethods.ErrorSuccess;
        }

        /// <summary>
        /// Attempts to retrieve the active ETW sessions on the box.
        /// </summary>
        /// <returns>
        /// An array with the name of the active ETW sessions on the box.
        /// </returns>
        public static string[] GetSessionNames()
        {
            lock (sharedBufferLock)
            {
                uint actualSessions = 0;
                var retryInCaseOfSmallBuffer = true;
                int result = (int)NativeMethods.ErrorSuccess;

                while (retryInCaseOfSmallBuffer)
                {
                    var requiredSize = expectedActiveEtwSessions * (TracePropertiesSize + ExtSize);
                    if (sharedBufferSize < requiredSize)
                    {
                        ReallocateSharedBufferSize((int)requiredSize);
                    }

                    NativeMethods.ZeroMemory(sharedBuffer, requiredSize);
                    var pointerArray = new IntPtr[expectedActiveEtwSessions];
                    for (int i = 0; i < expectedActiveEtwSessions; ++i)
                    {
                        var traceProperties = (NativeMethods.EventTraceProperties*)(
                            (char*)sharedBuffer + (i * (TracePropertiesSize + ExtSize)));
                        traceProperties->LoggerNameOffset = TracePropertiesSize;
                        traceProperties->LogFileNameOffset = TracePropertiesSize + (MaxNameSize * sizeof(char));
                        traceProperties->Wnode.BufferSize = TracePropertiesSize + ExtSize;
                        pointerArray[i] = (IntPtr)traceProperties;
                    }

                    fixed (void* pointersToPropertyArray = pointerArray)
                    {
                        result = NativeMethods.QueryAllTracesW(
                            pointersToPropertyArray, expectedActiveEtwSessions, ref actualSessions);

                        // There is a bug in QueryAllTracesW: it returns success even when there wasn't space to capture
                        // all active sessions and actualSessions ends up with the same value passed as expectedActiveEtwSessions.
                        // In general QueryAllTracesW does not return ERROR_MORE_DATA when there
                        // are more sessions than EVENT_TRACE_PROPERTIES in the array, so if the returned number of
                        // actual sessions is equal to the number of expected sessions it is necessary to try again
                        // until an array with more EVENT_TRACE_PROPERTIES pointers than actual sessions is passed.
                        if ((result == NativeMethods.ErrorMoreData || expectedActiveEtwSessions == actualSessions) &&
                            expectedActiveEtwSessions < MaxSessionsByQueryAllTraces)
                        {
                            expectedActiveEtwSessions = (expectedActiveEtwSessions < actualSessions)
                                                   ? actualSessions + 1
                                                   : 2 * expectedActiveEtwSessions;

                            expectedActiveEtwSessions = Math.Max(expectedActiveEtwSessions, MaxSessionsByQueryAllTraces);
                        }
                        else
                        {
                            retryInCaseOfSmallBuffer = false;
                        }
                    }
                }

                if (result != NativeMethods.ErrorSuccess)
                {
                    throw new Win32Exception(result, "Error calling QueryAllTracesW (0x" + result.ToString("X8") + ")");
                }

                // Capture the session names from the EVENT_TRACE_PROPERTIES structures.
                var sessionNames = new string[actualSessions];
                for (int i = 0; i < actualSessions; ++i)
                {
                    var traceProperties = (char*)sharedBuffer + (int)(i * (TracePropertiesSize + ExtSize));
                    sessionNames[i] = new string(
                        traceProperties + (int)((NativeMethods.EventTraceProperties*)traceProperties)->LoggerNameOffset);
                }

                return sessionNames;
            }
        }

        /// <summary>
        /// Helper method that allows to get information about the given provider on the specified ETW session.
        /// </summary>
        /// <param name="loggerId">
        /// The id of the ETW session for which the provider settings are going to be retrieved. This id can
        /// be retrieved using the return of the GetSessionProperties method via the <c>Wnode.HistoricalContext</c>
        /// field.
        /// </param>
        /// <param name="providerId">
        /// The id of the provider for which the session settings should be retrieved.
        /// </param>
        /// <param name="enableInfo">
        /// The struct containing information with the provider in the specified session.
        /// The struct just contains valid data if the method returns true.
        /// </param>
        /// <returns>
        /// True if the provider was enabled on the session, false otherwise.
        /// </returns>
        public static bool GetProviderInfo(ulong loggerId, Guid providerId, out NativeMethods.TraceEnableInfo enableInfo)
        {
            var foundProvider = false;
            enableInfo = default(NativeMethods.TraceEnableInfo);

            lock (sharedBufferLock)
            {
                int requiredBufferSize = 0;
                void* bufferPtr = null;
                var retryInCaseOfSmallBuffer = true;
                int result = (int)NativeMethods.ErrorSuccess;

                while (retryInCaseOfSmallBuffer)
                {
                    bufferPtr = sharedBuffer.ToPointer();
                    result = NativeMethods.EnumerateTraceGuidsEx(
                        NativeMethods.TraceQueryInfoClass.TraceGuidQueryInfo,
                        &providerId,
                        sizeof(Guid),
                        bufferPtr,
                        sharedBufferSize,
                        ref requiredBufferSize);

                    if (result != NativeMethods.ErrorInsufficientBuffer)
                    {
                        retryInCaseOfSmallBuffer = false;
                    }
                    else
                    {
                        ReallocateSharedBufferSize(requiredBufferSize);
                    }
                }

                if (result == NativeMethods.ErrorWmiGuidNotFound)
                {
                    // Provider not found
                    return false;
                }

                if (result != NativeMethods.ErrorSuccess)
                {
                    throw new Win32Exception(result, "Error calling EnumerateTraceGuidsEx (0x" + result.ToString("X8") + ")");
                }

                var traceGuidInfo = *((NativeMethods.TraceGuidInfo*)bufferPtr);
                bufferPtr = ((byte*)bufferPtr) + sizeof(NativeMethods.TraceGuidInfo);

                for (int i = 0; i < traceGuidInfo.InstanceCount && !foundProvider; ++i)
                {
                    var traceProviderInstanceInfo = *((NativeMethods.TraceProviderInstanceInfo*)bufferPtr);
                    if (traceProviderInstanceInfo.EnableCount > 0)
                    {
                        var traceEnableInfoPtr = (NativeMethods.TraceEnableInfo*)(
                            (byte*)bufferPtr + sizeof(NativeMethods.TraceProviderInstanceInfo));

                        for (int j = 0; j < traceProviderInstanceInfo.EnableCount; ++j)
                        {
                            // Only add information for the expected session
                            if (traceEnableInfoPtr->LoggerId == loggerId)
                            {
                                enableInfo = *traceEnableInfoPtr;
                                foundProvider = true;
                                break;
                            }

                            traceEnableInfoPtr++;
                        }
                    }

                    bufferPtr = (byte*)bufferPtr + traceProviderInstanceInfo.NextOffset;
                }
            }

            return foundProvider;
        }

        /// <summary>
        /// Attempts to get the current ETL file name of the given ETW session.
        /// </summary>
        /// <param name="sessionName">
        /// Name of the session to retrieve the current file.
        /// </param>
        /// <param name="currentSessionFile">
        /// Name of the ETL file currently being used by the session.
        /// </param>
        /// <returns>
        /// True if the operation to retrieve the current ETL file of the session was successful, false otherwise.
        /// </returns>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Not accessible to any 3rd-party MS or not")]
        public static bool TryGetCurrentFileOfSession(string sessionName, out string currentSessionFile)
        {
            currentSessionFile = null;
            lock (sharedBufferLock)
            {
                var traceProperties = new NativeMethods.EventTraceProperties
                {
                    LoggerNameOffset = TracePropertiesSize,
                    LogFileNameOffset = TracePropertiesSize + (MaxNameSize * sizeof(char)),
                    Wnode = new NativeMethods.WnodeHeader
                    {
                        BufferSize = TracePropertiesSize + ExtSize,
                    }
                };

                NativeMethods.ZeroMemory(sharedBuffer, traceProperties.Wnode.BufferSize);
                Marshal.StructureToPtr(traceProperties, sharedBuffer, true);

                var errorCode = NativeMethods.ControlTrace(
                    0,
                    sessionName,
                    sharedBuffer,
                    (int)NativeMethods.TraceControl.Query);

                var tracePropertiesPtr = (NativeMethods.EventTraceProperties*)sharedBuffer;
                if (errorCode == NativeMethods.ErrorSuccess && tracePropertiesPtr->LogFileNameOffset > 0)
                {
                    currentSessionFile = new string((char*)(tracePropertiesPtr + (int)tracePropertiesPtr->LogFileNameOffset));
                }

                return errorCode == NativeMethods.ErrorSuccess && currentSessionFile != null;
            }
        }

        /// <summary>
        /// Sends the specified control to the given session, captures the result of the
        /// call to ControlTrace and return the struct with the trace properties (its contents
        /// depend on the control passed to the function).
        /// </summary>
        /// <param name="sessionName">
        /// Name of the session to have the control applied against.
        /// </param>
        /// <param name="traceControl">
        /// Control to be applied against the session.
        /// </param>
        /// <param name="errorCode">
        /// Output parameter that receives the result of the call to ControlTrace.
        /// </param>
        /// <returns>
        /// The struct with the trace properties (its contents depend on the control passed to the function).
        /// </returns>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Not accessible to any 3rd-party MS or not")]
        private static NativeMethods.EventTraceProperties ControlTrace(
            string sessionName, NativeMethods.TraceControl traceControl, out int errorCode)
        {
            lock (sharedBufferLock)
            {
                var traceProperties = new NativeMethods.EventTraceProperties
                {
                    LoggerNameOffset = TracePropertiesSize,
                    LogFileNameOffset = TracePropertiesSize + (MaxNameSize * sizeof(char)),
                    Wnode = new NativeMethods.WnodeHeader
                    {
                        BufferSize = TracePropertiesSize + ExtSize,
                    }
                };

                NativeMethods.ZeroMemory(sharedBuffer, traceProperties.Wnode.BufferSize);
                Marshal.StructureToPtr(traceProperties, sharedBuffer, true);

                errorCode = NativeMethods.ControlTrace(
                    0,
                    sessionName,
                    sharedBuffer,
                    (uint)traceControl);

                traceProperties = (NativeMethods.EventTraceProperties)Marshal.PtrToStructure(
                    sharedBuffer, typeof(NativeMethods.EventTraceProperties));

                return traceProperties;
            }
        }

        /// <summary>
        /// Helper that allows change in the size of the shared buffer pointer.
        /// </summary>
        /// <param name="newBufferSize">
        /// New desired buffer size.
        /// </param>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Not accessible to any 3rd-party MS or not")]
        private static void ReallocateSharedBufferSize(int newBufferSize)
        {
            lock (sharedBufferLock)
            {
                var newSize = Math.Max(sharedBufferSize, newBufferSize);
                Marshal.FreeHGlobal(sharedBuffer);
                sharedBufferSize = newSize;
                sharedBuffer = Marshal.AllocHGlobal(sharedBufferSize);
            }
        }
    }
}
<file_sep>//---------------------------------------------------------------------------------
// <copyright file="ActiveCollector.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Security;
    using System.Text;

    using Logging;

    /// <summary>
    /// An instance of this type represents an active collector.
    /// </summary>
    /// <remarks>
    /// The type is dissociated from its configuration since this can be updated or changed manually
    /// outside of the control of the instance (e.g.: via <c>logman</c> by the user). So this type does not
    /// carry the configuration information instead it directly queries the system for the respective
    /// ETW session whenever it needs to retrieve the current settings.
    /// </remarks>
    /// <remarks>
    /// It started to be implemented using only logman but the functionality to retrieve the providers
    /// of an ETW session would require text parsing. In order to avoid that this class also uses the
    /// PLA API.
    /// </remarks>
    internal sealed class ActiveCollector
    {
        /// <summary>
        /// Exit code value that indicates success on the call to a program.
        /// </summary>
        private const int ExitCodeSuccess = 0;

        /// <summary>
        /// Custom log id to be used in the log statements.
        /// </summary>
        private static readonly object LogId = Logger.CreateCustomLogId("ActiveCollector");

        /// <summary>
        /// Root directory where the ETL files created and manipulated via this instance are going to be created.
        /// </summary>
        private readonly string baseEtlLocation;

        /// <summary>
        /// Flag to indicate if this is a file collector or not.
        /// </summary>
        private bool isFileCollector;

        /// <summary>
        /// For collectors writing to files this keeps the base name for the ETL files.
        /// It should be used only for file collectors.
        /// </summary>
        private string etlBaseName;

        /// <summary>
        /// Keeps the maximum number of files that should be kept for collectors writing
        /// ETL files. It should be used only for file collectors.
        /// </summary>
        private int maxFileCount;

        /// <summary>
        /// Keeps the maximum size that is desired for each ETL file.
        /// It should be used only for file collectors.
        /// </summary>
        private int maxFileSizeKB;

        /// <summary>
        /// Keeps the desired maximum time span of each ETL file.
        /// It should be used only for file collectors.
        /// </summary>
        private TimeSpan maxFileTimeSpan;

        /// <summary>
        /// Gets the time that an actual file rotation was performed.
        /// </summary>
        private DateTime lastRotationTime;

        /// <summary>
        /// Keeps track of the current ETL file of the collector, it will be null if it is a real-time collector.
        /// </summary>
        private string currentEtlSessionFile;

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveCollector"/> class.
        /// </summary>
        /// <param name="sessionName">
        /// The name of the ETW session to be used by the collector.
        /// </param>
        /// <param name="baseEtlLocation">
        /// If the collector creates ETL files this is the base location where they should be created.
        /// </param>
        public ActiveCollector(string sessionName, string baseEtlLocation = ".")
        {
            if (string.IsNullOrEmpty(sessionName))
            {
                throw new ArgumentException("sessionName cannot be null or empty.", "sessionName");
            }

            if (string.IsNullOrEmpty(baseEtlLocation))
            {
                throw new ArgumentException("baseEtlLocation cannot be null or empty.", "baseEtlLocation");
            }

            this.Name = sessionName;
            this.lastRotationTime = DateTime.MaxValue;
            this.baseEtlLocation = baseEtlLocation;
        }

        /// <summary>
        /// Gets the name of the session associated to the collector.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Get the directory where ETL files for this collector should be written.
        /// It should be used only for file collectors.
        /// </summary>
        public string EtlLogsDirectory { get; private set; }

        /// <summary>
        /// Stops collector with the given sessionName.
        /// </summary>
        /// <param name="collectorName">
        /// Name of the collector to be stopped.
        /// </param>
        /// <returns>
        /// True if the operation succeeded, false otherwise.
        /// </returns>
        public static bool StopCollector(string collectorName)
        {
            const string MethodName = "StopCollector";

            if (string.IsNullOrEmpty(collectorName))
            {
                throw new ArgumentException("collectorName cannot be null or empty.", "collectorName");
            }

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                MethodName,
                "Attempting to stop ETW session [{0}]",
                collectorName);
            if (!EtwSessionManager.Stop(collectorName))
            {
                Logger.Log(
                    LoggerLevel.Error,
                    LogId,
                    MethodName,
                    "Failed to stop ETW session [{0}]",
                    collectorName);
                return false;
            }

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                MethodName,
                "ETW session [{0}] was stopped",
                collectorName);

            return true;
        }

        /// <summary>
        /// Update the collector session with the provider settings specified on the configuration.
        /// </summary>
        /// <param name="config">
        /// The configuration of the collector.
        /// </param>
        /// <returns>True if update providers succeeded.</returns>
        public static bool TryUpdateProviders(CollectorConfiguration config)
        {
            bool result = true;
            foreach (var provider in config.Providers.Values)
            {
                var args = string.Format(
                    CultureInfo.InvariantCulture,
                    "update \"{0}\" -p {1} 0x{2},0x{3} {4} -ets",
                    config.Name,
                    provider.Id.ToString("B"),
                    provider.KeywordsAny.ToString("X16"),
                    provider.KeywordsAll.ToString("X16"),
                    ((int)provider.Level).ToString(CultureInfo.InvariantCulture));
                var exitCode = RunCommand("logman", args);
                if (exitCode != ExitCodeSuccess)
                {
                    Logger.Log(
                        LoggerLevel.Error,
                        LogId,
                        "UpdateProviders",
                        "Failed to set provider {0} on collector [{1}]. Error code: 0x{2}",
                        provider.Id.ToString("B"),
                        config.Name,
                        exitCode.ToString("X8"));
                    result = false;
                }
            }

            return result;
        }

        /// <summary>
        /// Starts the collector with the given configuration.
        /// </summary>
        /// <param name="config">
        /// The configuration of the collector to be started.
        /// </param>
        /// <returns>
        /// The complete list with the back log of ETL files associated to the collector from oldest to newest.
        /// </returns>
        public List<string> StartCollector(CollectorConfiguration config)
        {
            const string MethodName = "StartCollector";

            var etlBacklog = new List<string>();

            // If a deprecated session exists try to shut it down
            NativeMethods.EventTraceProperties traceProperties;
            if (!string.IsNullOrEmpty(config.DeprecatedCollector))
            {
                StopCollector(config.DeprecatedCollector);
            }

            var clockType = config.ClockType == ClockType.Default ? ClockType.Perf : config.ClockType;
            var preExistingSession = false;
            if (EtwSessionManager.TryGetSessionProperties(this.Name, out traceProperties))
            {
                if ((NativeMethods.EtwSessionClockType)clockType == traceProperties.Wnode.ClientContext)
                {
                    // Old session can be re-used.
                    preExistingSession = true;
                }
                else
                {
                    // Old session needs to be stopped. Note that if stop failed there is a follow up check to ensure that
                    // the error was not due to the session being stopped between the checks.
                    if (!StopCollector(this.Name))
                    {
                        Logger.Log(
                            LoggerLevel.Error,
                            LogId,
                            MethodName,
                            "Failed to stop existing trace session [{0}]. Cannot proceed to correctly update session.",
                            this.Name);
                        return etlBacklog;
                    }
                }
            }

            StringBuilder sb = new StringBuilder(256);
            sb.AppendFormat(
                CultureInfo.InvariantCulture,
                "{0} \"{1}\" -nb {2} {3} -bs {4} -ft {5} -ct {6} -ets ",
                preExistingSession ? "update" : "start",
                config.Name,
                config.MinBufferCount.ToString(CultureInfo.InvariantCulture),
                config.MaxBufferCount.ToString(CultureInfo.InvariantCulture),
                config.BufferSizeKB.ToString(CultureInfo.InvariantCulture),
                config.FlushTimerSec.ToString(CultureInfo.InvariantCulture),
                clockType);

            if (config.SessionType == SessionType.Realtime)
            {
                sb.Append("-rt");
            }
            else
            {
                // Add parameters for proper ETL file rotation
                this.isFileCollector = true;
                this.maxFileCount = config.MaxFileCount;
                this.maxFileSizeKB = config.MaxFileSizeMB * 1024;
                this.maxFileTimeSpan = config.MaxFileTimeSpan;
                this.etlBaseName = config.OriginalName;
                this.EtlLogsDirectory = Path.Combine(this.baseEtlLocation, config.OriginalName);

                if (!ProtectedIO(
                    () => Directory.CreateDirectory(this.EtlLogsDirectory),
                    e =>
                    {
                        Logger.Log(
                            LoggerLevel.Error,
                            LogId,
                            MethodName,
                            "Failed to create directory [{0}] for collector [{1}]. Exception: {2}",
                            this.EtlLogsDirectory,
                            config.Name,
                            e);
                        Logger.Log(
                            LoggerLevel.Error,
                            LogId,
                            MethodName,
                            "Failed to create or update ETW session [{0}]",
                            config.Name);
                    }))
                {
                    return etlBacklog;
                }

                // Get the full list of available ETL files, it will be trimmed according to success of update command.
                etlBacklog = this.GetExistingEtlFiles();

                this.currentEtlSessionFile = this.GenerateNextSessionFileName();
                sb.AppendFormat(CultureInfo.InvariantCulture, "-mode Sequential -o \"{0}\"", this.currentEtlSessionFile);

                if (config.SessionType == SessionType.FileAndRealtime
                    || config.SessionType == SessionType.RealtimeAndFile)
                {
                    sb.Append(" -rt");
                }
            }

            var exitCode = RunCommand("logman", sb.ToString());
            this.lastRotationTime = DateTime.UtcNow;
            if (exitCode != ExitCodeSuccess)
            {
                Logger.Log(
                    LoggerLevel.Error,
                    LogId,
                    MethodName,
                    "Logman failed to create or update ETW session [{0}].",
                    config.Name);

                if (preExistingSession)
                {
                    if (config.SessionType != SessionType.Realtime)
                    {
                        if (!EtwSessionManager.TryGetCurrentFileOfSession(config.Name, out this.currentEtlSessionFile))
                        {
                            Logger.Log(
                                LoggerLevel.Error,
                                LogId,
                                MethodName,
                                "Failed to retrieve name of the ETL being used by ETW session [{0}].",
                                config.Name);
                        }

                        if (etlBacklog.Count > 0
                            && string.Compare(
                                etlBacklog.Last(), this.currentEtlSessionFile, StringComparison.OrdinalIgnoreCase) == 0)
                        {
                            etlBacklog.RemoveAt(etlBacklog.Count - 1);
                            Logger.Log(
                                LoggerLevel.Warning,
                                LogId,
                                MethodName,
                                "Current ETL file removed from backlog list since it is still in use. ETL File [{0}]",
                                this.currentEtlSessionFile);
                        }
                    }

                    Logger.Log(
                        LoggerLevel.Info,
                        LogId,
                        MethodName,
                        "Attempting to mitigate with pre-existing session...");
                    if (!ExistingSessionSatisfiesProviders(config))
                    {
                        Logger.Log(
                            LoggerLevel.Error,
                            LogId,
                            "StartCollector",
                            "Pre-existing session cannot be used since it does not satisfy the config.");
                    }
                    else
                    {
                        Logger.Log(
                            LoggerLevel.Error,
                            LogId,
                            MethodName,
                            "Using pre-existing ETW session since it satisfied the configuration.");
                        exitCode = ExitCodeSuccess;
                    }
                }
            }

            if (exitCode == ExitCodeSuccess || preExistingSession)
            {
                Logger.Log(
                    LoggerLevel.Info,
                    LogId,
                    MethodName,
                    "ETW session is in place call UpdateProviders to enable them.");
            }

            etlBacklog.Sort(StringComparer.OrdinalIgnoreCase);
            return etlBacklog;
        }

        /// <summary>
        /// Rotates the session ETL file according to the current client configuration, taking care of
        /// deleting old files if necessary. Returns true if a file was actually rotated.
        /// </summary>
        /// <param name="referenceTime">
        /// The time for which the method should calculate if a file needs to be rotated or not.
        /// </param>
        /// <param name="closedSessionFile">
        /// Output parameter that will receive the name of the ETL file that was just closed in case
        /// an actual file rotation was performed. It is meaningful only if the method returns true.
        /// </param>
        /// <returns>
        /// True if an actual file rotation was performed, otherwise it returns false.
        /// </returns>
        public bool RotateSessionFile(DateTime referenceTime, out string closedSessionFile)
        {
            var rotatedFile = false;
            closedSessionFile = null;

            if (this.isFileCollector)
            {
                // The rotation can be triggered in two conditions:
                // 1. If the time for the current file passed
                // 2. If the current size of the file exceeds the threshold
                var attemptRotation =
                    ((referenceTime - this.lastRotationTime) > this.maxFileTimeSpan) ||
                    (this.GetCurrentFileSize() > this.maxFileSizeKB);

                if (attemptRotation)
                {
                    var nextFileName = this.GenerateNextSessionFileName();
                    var args = string.Format(CultureInfo.InvariantCulture, "update \"{0}\" -o \"{1}\" -ets", this.Name, nextFileName);
                    var exitCode = RunCommand("logman", args);
                    if (exitCode == ExitCodeSuccess)
                    {
                        rotatedFile = true;
                        closedSessionFile = this.currentEtlSessionFile;
                        this.currentEtlSessionFile = nextFileName;
                        this.lastRotationTime = DateTime.UtcNow;
                        this.DeleteOlderSessionFiles();
                    }
                }
            }

            return rotatedFile;
        }

        /// <summary>
        /// Checks if an existing ETW session satisfies the given collector configuration.
        /// </summary>
        /// <param name="config">
        /// Configuration that should be checked against the existing ETW session.
        /// </param>
        /// <returns>
        /// True if the existing session satisfies the given configuration, false otherwise.
        /// </returns>
        internal static bool ExistingSessionSatisfiesProviders(CollectorConfiguration config)
        {
            // Ok, the session is already in place, just check the providers
            bool providersOk = false;
            NativeMethods.EventTraceProperties sessionProperties;
            if (EtwSessionManager.TryGetSessionProperties(config.Name, out sessionProperties))
            {
                providersOk = true;

                // Loop over providers and ensure that they are good
                var loggerId = sessionProperties.Wnode.HistoricalContext;
                foreach (var provider in config.Providers)
                {
                    NativeMethods.TraceEnableInfo providerInSession;
                    providersOk = EtwSessionManager.GetProviderInfo(loggerId, provider.Value.Id, out providerInSession)
                                  && providerInSession.IsEnabled != 0
                                  && provider.Value.Level <= (EtwTraceLevel)providerInSession.Level
                                  && (provider.Value.KeywordsAll | providerInSession.MatchAllKeyword) == provider.Value.KeywordsAll
                                  && (provider.Value.KeywordsAny & providerInSession.MatchAnyKeyword) == provider.Value.KeywordsAny;

                    if (!providersOk)
                    {
                        // pass the native type to a provider to get a nice string representing the
                        // current settings
                        var providerConfigInSession = new ProviderConfiguration(
                            provider.Value.Id,
                            (EtwTraceLevel)providerInSession.Level,
                            providerInSession.MatchAnyKeyword,
                            providerInSession.MatchAllKeyword);
                        Logger.Log(
                            LoggerLevel.Error,
                            LogId,
                            "ExistingSessionSatisfiesProviders",
                            "Provider configuration [{0}] is not satisfied in ETW session [{1}]. Actual provider settings in session: {2}",
                            provider.Value,
                            config.Name,
                            providerInSession.IsEnabled == 0 ? "provider not enabled" : providerConfigInSession.ToString());
                    }
                }
            }

            return providersOk;
        }

        /// <summary>
        /// Disables a specific provider in a given collector.
        /// </summary>
        /// <param name="collectorName">
        ///     The sessionName of the collector in which the provider should be disabled.
        /// </param>
        /// <param name="providerId">
        ///     The Id of provider that should be disabled.
        /// </param>
        private static void DisableProvider(string collectorName, Guid providerId)
        {
            var args = string.Format(
                CultureInfo.InvariantCulture,
                "update \"{0}\" --p {1} -ets",
                collectorName,
                providerId.ToString("B"));

            // Ignore any error when stopping a provider, information already logged inside RunCommand
            RunCommand("logman", args);
        }

        /// <summary>
        /// Executes a command-line application and returns its exit code.
        /// </summary>
        /// <param name="fileName">
        /// Name of the application to be executed.
        /// </param>
        /// <param name="arguments">
        /// Command-line arguments to be passed to the application.
        /// </param>
        /// <returns>
        /// Exit code returned by the application.
        /// </returns>
        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Not accessible to any 3rd-party MS or not")]
        private static int RunCommand(string fileName, string arguments)
        {
            const string MethodName = "RunCommand";

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                MethodName,
                "[{0} {1}]",
                fileName,
                arguments);

            using (var p = new Process())
            {
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.FileName = fileName;
                p.StartInfo.Arguments = arguments;
                p.StartInfo.CreateNoWindow = true;

                p.Start();
                while (!p.StandardOutput.EndOfStream)
                {
                    string line = p.StandardOutput.ReadLine();
                    Logger.Log(LoggerLevel.Info, LogId, MethodName, "\t\t{0}", line);
                }

                var win32Exception = new Win32Exception(p.ExitCode);
                Logger.Log(
                    LoggerLevel.Info,
                    LogId,
                    MethodName,
                    "exitCode=0x{0}: {1}",
                    p.ExitCode.ToString("X8"),
                    win32Exception.Message);

                return p.ExitCode;
            }
        }

        /// <summary>
        /// Performs an IO operation catching all IO, security and unauthorized access
        /// exceptions.
        /// </summary>
        /// <param name="ioOperation">
        /// IO operation to be performed.
        /// </param>
        /// <param name="failureAction">
        /// Action to be executed in case an exception is raised by the IO operation.
        /// Note that this action takes the raised exception was a parameter.
        /// </param>
        /// <returns>
        /// True if the IO operation did not raise any exception, false otherwise.
        /// </returns>
        private static bool ProtectedIO(Action ioOperation, Action<Exception> failureAction)
        {
            Exception exception = null;
            try
            {
                ioOperation();
            }
            catch (IOException e)
            {
                exception = e;
            }
            catch (SecurityException e)
            {
                exception = e;
            }
            catch (UnauthorizedAccessException e)
            {
                exception = e;
            }

            if (exception != null)
            {
                failureAction(exception);
            }

            return exception == null;
        }

        /// <summary>
        /// Builds the name of the next ETL file.
        /// </summary>
        /// <returns>
        /// The name for the next ETL for the session.
        /// </returns>
        private string GenerateNextSessionFileName()
        {
            // ATTENTION: during rotation of ETL files the deletion code assumes that if the ETL files
            // are sorted according to their names (ordinal and ignore case) the older ones are going
            // to be the first ones. Be careful if changing the resulting file name.
            var dateTime = DateTime.UtcNow;
            var nextSessionFileName = string.Format(
                CultureInfo.CurrentCulture,
                "{0}_{1:0000}-{2:00}-{3:00}_{4:00}-{5:00}-{6:00}_utc.etl",
                this.etlBaseName,
                dateTime.Year,
                dateTime.Month,
                dateTime.Day,
                dateTime.Hour,
                dateTime.Minute,
                dateTime.Second);
            return Path.Combine(this.EtlLogsDirectory, nextSessionFileName);
        }

        /// <summary>
        /// Deletes older session files associated to the collector.
        /// </summary>
        /// <remarks>
        /// In order for this method to work correctly it depends that a sort by file names
        /// should produce the same sequence as sorting the files by their creation date.
        /// </remarks>
        private void DeleteOlderSessionFiles()
        {
            if (Directory.Exists(this.EtlLogsDirectory))
            {
                List<string> existingFiles = this.GetExistingEtlFiles();

                var totalFilesToDelete = existingFiles.Count - this.maxFileCount;
                if (totalFilesToDelete > 0)
                {
                    // ATTENTION: Sorting file names should give the older files in front of the list
                    existingFiles.Sort(StringComparer.OrdinalIgnoreCase);
                    for (int i = 0; i < totalFilesToDelete; ++i)
                    {
                        var existingFile = existingFiles[i];
                        ProtectedIO(
                            () => File.Delete(existingFile),
                            e => Logger.Log(
                                    LoggerLevel.Warning,
                                    LogId,
                                    "DeleteOlderSessionFiles",
                                    "Failed to delete ETL file [{0}]. Exception: {1}",
                                    existingFile,
                                    e.Message));
                    }
                }
            }
        }

        /// <summary>
        /// Gets the ETL files associated to this collector.
        /// </summary>
        /// <returns>
        /// The list of ETL files generated by the collector so far.
        /// </returns>
        private List<string> GetExistingEtlFiles()
        {
            List<string> existingFiles = null;
            var searchPattern = this.etlBaseName + "_*.etl";

            // ReSharper disable ImplicitlyCapturedClosure
            if (!ProtectedIO(
                    () => existingFiles = Directory.GetFiles(this.EtlLogsDirectory, searchPattern).ToList(),
                    e =>
                    Logger.Log(
                        LoggerLevel.Warning,
                        LogId,
                        "GetExistingEtlFiles",
                        "Failed to enumerate files at [{0}] with mask [{1}] for collector [{2}]. Exception: {3}",
                        this.EtlLogsDirectory,
                        searchPattern,
                        this.Name,
                        e.Message)))
            {
                return new List<string>();
            }

            // ReSharper restore ImplicitlyCapturedClosure
            return existingFiles;
        }

        /// <summary>
        /// Gets the size of the current ETL file, in kilobytes, being written by the collector.
        /// </summary>
        /// <returns>
        /// The current size of the ETL file being written by the collector.
        /// </returns>
        private uint GetCurrentFileSize()
        {
            if (!this.isFileCollector)
            {
                throw new InvalidOperationException("Tried to obtain file size for the non-file collector [" + this.Name + "]");
            }

            throw new NotImplementedException();
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="DistinctCountConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;

    /// <summary>
    /// Configures distinct count feature on this preaggregate.
    /// </summary>
    public sealed class DistinctCountConfiguration : IDistinctCountConfiguration
    {
        private readonly List<string> dimensions;

        /// <summary>
        /// Initializes a new instance of the <see cref="DistinctCountConfiguration"/> class.
        /// </summary>
        public DistinctCountConfiguration()
        {
            this.dimensions = new List<string>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DistinctCountConfiguration"/> class.
        /// </summary>
        /// <param name="dimensions">The dimensions.</param>
        [JsonConstructor]
        internal DistinctCountConfiguration(IEnumerable<string> dimensions)
        {
            this.dimensions = dimensions?.ToList() ?? new List<string>();
        }

        /// <summary>
        /// Gets the dimensions.
        /// </summary>
        public IEnumerable<string> Dimensions
        {
            get { return this.dimensions; }
        }

        /// <summary>
        /// Adds the dimension.
        /// </summary>
        /// <param name="dimensionToAdd">The dimension to add.</param>
        public void AddDimension(string dimensionToAdd)
        {
            if (string.IsNullOrWhiteSpace(dimensionToAdd))
            {
                throw new ArgumentNullException(nameof(dimensionToAdd));
            }

            if (this.dimensions.Count == 0)
            {
                this.dimensions.Add(dimensionToAdd);
                return;
            }

            for (var i = 0; i < this.dimensions.Count; ++i)
            {
                var comparison = string.Compare(this.dimensions[i], dimensionToAdd, StringComparison.OrdinalIgnoreCase);
                if (comparison == 0)
                {
                    throw new ConfigurationValidationException("Cannot add duplicate dimensions.", ValidationType.DuplicateDimension);
                }

                if (comparison > 0)
                {
                    this.dimensions.Insert(i, dimensionToAdd);
                    return;
                }

                if ((i + 1) == this.dimensions.Count)
                {
                    this.dimensions.Add(dimensionToAdd);
                    return;
                }
            }
        }

        /// <summary>
        /// Removes the dimension.
        /// </summary>
        /// <param name="dimension">The dimension.</param>
        public void RemoveDimension(string dimension)
        {
            this.dimensions.RemoveAll(x => string.Equals(x, dimension, StringComparison.OrdinalIgnoreCase));
        }
    }
}
<file_sep>// -----------------------------------------------------------------------
// <copyright file="ProviderConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;

    /// <summary>
    /// Type that reads the settings of a single provider from a configuration file.
    /// </summary>
    /// <remarks>
    /// The value of a provider key is expected to be in the following format:
    /// <para/>&lt;GUID&gt;[,&lt;parameterPair&gt;]*<para/>
    /// In which a parameterPair has the following format "parameterName:parameterValue".
    /// Below is a list of some valid provider configurations with respective keys:
    /// <para/>
    /// Provider1 = {D857C50C-9002-4852-94A4-7264063CF38D}
    /// Provider2 = {9FD91669-452C-4B25-AD5B-5322D511DA65},level:Informational,KeywordsAny:0x1f0
    /// Provider3 = {B7F33BAA-E45A-4FCF-8389-FA103A2AC23C},KeywordsAll:0x5
    /// <para>
    /// None of the parameters is mandatory, for all of them a default value is provided if not
    /// specified on the configuration.
    /// </para>
    /// </remarks>
    internal sealed class ProviderConfiguration
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ProviderConfiguration"/> class with the specific
        /// values given by the caller.
        /// </summary>
        /// <param name="id">
        /// The id that identifies the provider.
        /// </param>
        /// <param name="level">
        /// The logging level from which events should be logged.
        /// </param>
        /// <param name="keywordsAny">
        /// The "keywords any" value to be used when enabling this provider.
        /// </param>
        /// <param name="keywordsAll">
        /// The "keywords all" value to be used when enabling this provider.
        /// </param>
        public ProviderConfiguration(Guid id, EtwTraceLevel level, long keywordsAny, long keywordsAll)
        {
            this.Id = id;
            this.Level = level;
            this.KeywordsAny = keywordsAny;
            this.KeywordsAll = keywordsAll;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ProviderConfiguration"/> class.
        /// </summary>
        private ProviderConfiguration()
        {
        }

        /// <summary>
        /// Gets the unique id of the provider.
        /// </summary>
        public Guid Id { get; private set; }

        /// <summary>
        /// Gets the level from which events should be logged.
        /// </summary>
        public EtwTraceLevel Level { get; private set; }

        /// <summary>
        /// Gets the match any keyword value to be used when enabling this provider.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd392305(v=vs.85).aspx"/>
        public long KeywordsAny { get; private set; }

        /// <summary>
        /// Gets the match all keyword value to be used when enabling this provider.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd392305(v=vs.85).aspx"/>
        public long KeywordsAll { get; private set; }

        /// <summary>
        /// Overriding ToString method to help tests and debugging.
        /// </summary>
        /// <returns>
        /// The <see cref="string"/> representing the provider configuration.
        /// </returns>
        public override string ToString()
        {
            return string.Format(
                "Id: {0} Level: {1} KeywordsAny: 0x{2} KeywordsAll: 0x{3}",
                this.Id.ToString("B"),
                this.Level.ToString(),
                this.KeywordsAny.ToString("X16"),
                this.KeywordsAll.ToString("X16"));
        }

        /// <summary>
        /// Creates a new instance cloned from the current one.
        /// </summary>
        /// <returns>
        /// The new <see cref="ProviderConfiguration"/> instance cloned from the current
        /// instance.
        /// </returns>
        public ProviderConfiguration Clone()
        {
            var clone = new ProviderConfiguration();
            clone.Id = this.Id;
            clone.Level = this.Level;
            clone.KeywordsAny = this.KeywordsAny;
            clone.KeywordsAll = this.KeywordsAll;

            return clone;
        }

        /// <summary>
        /// The merge the provider configuration with other instance when the provider
        /// is being enabled.
        /// </summary>
        /// <param name="otherConfiguration">
        /// The other configuration instance to be merged for enable.
        /// </param>
        public void MergeForEnable(ProviderConfiguration otherConfiguration)
        {
            if (this.Id == otherConfiguration.Id)
            {
                this.Level = this.Level > otherConfiguration.Level ? this.Level : otherConfiguration.Level;
                this.KeywordsAny |= otherConfiguration.KeywordsAny;
                this.KeywordsAll &= otherConfiguration.KeywordsAll;
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IQueryResultListV3.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Holds the list of query results and the associated metadata.
    /// </summary>
    public interface IQueryResultListV3
    {
        /// <summary>
        /// Gets the end time in UTC for the query results.
        /// </summary>
        DateTime EndTimeUtc { get; }

        /// <summary>
        /// Gets the start time in UTC for the query results.
        /// </summary>
        DateTime StartTimeUtc { get; }

        /// <summary>
        /// Gets the time resolution in milliseconds for the query results.
        /// </summary>
        int TimeResolutionInMinutes { get; }

        /// <summary>
        /// Gets the query results. Each result represent a single time series where start time, end time and time resolution
        /// is represented by this object members.
        /// </summary>
        IReadOnlyList<IQueryResultV3> Results { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CertificateHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.Linq;
    using System.Security;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;

    /// <summary>
    /// The certificate helper class
    /// </summary>
    internal class CertificateHelper
    {
        /// <summary>
        /// Finds and validates certificate.
        /// </summary>
        /// <param name="certificateThumbprint">The certificate thumbprint.</param>
        /// <param name="certificateStoreLocation">The certificate store location.</param>
        /// <returns>The certificate if validated.</returns>
        internal static X509Certificate2 FindAndValidateCertificate(string certificateThumbprint, StoreLocation certificateStoreLocation)
        {
            X509Certificate2 cert = FindX509Certificate(certificateThumbprint, certificateStoreLocation);
            if (!cert.HasPrivateKey)
            {
                throw new MetricsClientException(string.Format("Cert with Thumbprint [{0}] doesn't have a private key", cert.Thumbprint));
            }

            // Check expire and effective date
            DateTime now = DateTime.Now;
            if (cert.NotBefore > now)
            {
                throw new MetricsClientException(string.Format("The certificate is not valid until {0}.", cert.GetEffectiveDateString()));
            }

            if (cert.NotAfter < now)
            {
                throw new MetricsClientException(string.Format("The certificate is not valid after {0}.", cert.GetExpirationDateString()));
            }

            try
            {
                if (cert.PrivateKey == null)
                {
                    throw new MetricsClientException("The certificate has a private key but the PrivateKey property is null, and it is typically due to a permission issue.");
                }
            }
            catch (CryptographicException)
            {
                throw new MetricsClientException("The certificate has a private key but the PrivateKey property is null, and it is typically due to a permission issue.");
            }

            return cert;
        }

        /// <summary>
        /// Finds the X509 certificate.
        /// </summary>
        /// <param name="thumbprint">The thumbprint.</param>
        /// <param name="storeLocation">The store location.</param>
        /// <returns>
        /// The <see cref="X509Certificate2" /> certificate if found.
        /// </returns>
        private static X509Certificate2 FindX509Certificate(string thumbprint, StoreLocation storeLocation)
        {
            var store = new X509Store(StoreName.My, storeLocation);
            store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

            var certificates = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false);
            if (certificates.Count == 0)
            {
                throw new MetricsClientException(
                    string.Format("No cert with Thumbprint [{0}] is found in the [{1}] store", thumbprint, storeLocation));
            }

            var cert = certificates.OfType<X509Certificate2>().FirstOrDefault(c => c.HasPrivateKey);
            if (cert == null)
            {
                throw new MetricsClientException(string.Format("No cert with Thumbprint [{0}] has a private key", thumbprint));
            }

            return cert;
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="MinMaxConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// Determine if the minimum and maximum sampling types are available for this preaggregate.
    /// </summary>
    public sealed class MinMaxConfiguration : IMinMaxConfiguration
    {
        /// <summary>
        /// MinMaxConfiguration where minimum and maximum sampling types are enabled.
        /// </summary>
        public static readonly MinMaxConfiguration MinMaxEnabled = new MinMaxConfiguration(true);

        /// <summary>
        /// MinMaxConfiguration where minimum and maximum sampling types are disabled.
        /// </summary>
        public static readonly MinMaxConfiguration MinMaxDisabled = new MinMaxConfiguration(false);

        /// <summary>
        /// Initializes a new instance of the <see cref="MinMaxConfiguration"/> class.
        /// </summary>
        /// <param name="enabled">Whether or not the feature is enabled.</param>
        [JsonConstructor]
        internal MinMaxConfiguration(bool enabled)
        {
            this.Enabled = enabled;
        }

        /// <summary>
        /// Determines if the minimum and maximum sampling types are enabled or disabled.
        /// </summary>
        public bool Enabled { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HyperLogLogSketch.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using global::Metrics.Services.Common;

    /// <summary>
    /// Represents a hyperloglog sketch.
    /// </summary>
    public class HyperLogLogSketch : IPoolTrackable
    {
        /// <summary>
        /// Maximum value of B.
        /// </summary>
        public const int MaxBValue = 14;

        /// <summary>
        /// Maximum value of B.
        /// </summary>
        public const int DefaultBValue = 10;

        /// <summary>
        /// The b value.
        /// </summary>
        private readonly byte bValue;

        /// <summary>
        /// The registers.
        /// </summary>
        private byte[] registers;

        /// <summary>
        /// Initializes a new instance of the <see cref="HyperLogLogSketch"/> class.
        /// </summary>
        /// <param name="bValue">HyperLogLog B value</param>
        /// <param name="initializeRegisters">Flag to indicate whether to initialize registers.</param>
        public HyperLogLogSketch(int bValue, bool initializeRegisters = true)
        {
            if (initializeRegisters)
            {
                this.registers = new byte[1 << bValue];
            }

            this.bValue = (byte)bValue;
        }

        /// <summary>
        /// Gets the B value for sketch.
        /// </summary>
        public byte BValue => this.bValue;

        /// <summary>
        /// Gets the registers associated with the sketch.
        /// </summary>
        public byte[] Registers
        {
            get
            {
                return this.registers;
            }

            protected set
            {
                this.registers = value;
            }
        }

        /// <inheritdoc />
        PoolObjectTrackingInfo IPoolTrackable.PoolObjectTrackingInfo { get; set; }

        /// <summary>
        /// Sets the register value for given key.
        /// </summary>
        /// <param name="key">Index value.</param>
        /// <returns>
        /// Register value at given key.
        /// </returns>
        public byte this[int key]
        {
            get
            {
                return this.registers[key];
            }

            set
            {
                this.registers[key] = value;
            }
        }

        /// <summary>
        /// Initializes the Sketch.
        /// </summary>
        public void Reset()
        {
            for (var i = 0; i < this.registers.Length; i++)
            {
                this.registers[i] = 0;
            }
        }

        /// <summary>
        /// Aggregates the given sketch to this sketch.
        /// </summary>
        /// <param name="other">Other sketch.</param>
        public void Aggregate(HyperLogLogSketch other)
        {
            if (other.BValue != this.bValue)
            {
                // We do not support aggregation of non-aligned buffers.
                return;
            }

            for (var i = 0; i < this.registers.Length; i++)
            {
                if (this.registers[i] < other.registers[i])
                {
                    this.registers[i] = other.registers[i];
                }
            }
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="MonitorConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Monitors
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Configuration;
    using Logging;
    using Metrics;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Serialization;
    using Utility;

    /// <summary>
    /// Monitor configuration manager.
    /// </summary>
    public sealed class MonitorConfigurationManager : IMonitorConfigurationManager
    {
        private static readonly object LogId = Logger.CreateCustomLogId(nameof(MonitorConfigurationManager));
        private static readonly string LogTag = nameof(MonitorConfigurationManager);
        private readonly ConnectionInfo connectionInfo;
        private readonly HttpClient httpClient;
        private readonly string metricUrlPrefix;
        private readonly IMetricReader metricReader;

        private readonly JsonSerializerSettings serializerSettings = new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.Auto,
            ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = new CamelCaseNamingStrategy
                {
                    ProcessDictionaryKeys = false
                }
            }
        };

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitorConfigurationManager" /> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the endpoint being used.</param>
        public MonitorConfigurationManager(ConnectionInfo connectionInfo)
            : this(connectionInfo, HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo), new MetricReader(connectionInfo))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitorConfigurationManager" /> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the endpoint being used.</param>
        /// <param name="httpClient">The HTTP client.</param>
        /// <param name="metricReader">The metric reader.</param>
        internal MonitorConfigurationManager(ConnectionInfo connectionInfo, HttpClient httpClient, IMetricReader metricReader)
        {
            this.connectionInfo = connectionInfo;
            this.metricUrlPrefix = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.ConfigRelativeUrl);
            this.httpClient = httpClient;
            this.metricReader = metricReader;
        }

        /// <summary>
        /// Gets or sets the maximum parallel running tasks.
        /// </summary
        public int MaxParallelRunningTasks { get; set; } = 20;

        /// <inheritdoc />
        public async Task<ConfigurationUpdateResultList> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            string metricName,
            bool skipVersionCheck = false,
            bool validate = true)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (metricNamespace == null)
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (metricName == null)
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            var operation = $"{this.metricUrlPrefix}/replicateMonitorConfigurations";
            var path =
                $"{operation}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{metricNamespace}/metricName/{metricName}/skipVersionCheck/{skipVersionCheck}/operation/Replace";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetGlobalEndpoint())
            {
                Path = path,
                Query = $"validate={validate}"
            };

            var result = new ConfigurationUpdateResultList
            {
                MonitoringAccount = monitoringAccount.Name,
                MetricNamespace = metricNamespace,
                MetricName = metricName
            };

            try
            {
                if (monitoringAccount.MirrorMonitoringAccountList == null || !monitoringAccount.MirrorMonitoringAccountList.Any())
                {
                    throw new Exception("MirrorAccountsList can't be null or empty while replicating monitors.");
                }

                var serializedTargetAccounts = JsonConvert.SerializeObject(monitoringAccount.MirrorMonitoringAccountList.ToList(), Formatting.Indented, this.serializerSettings);
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Post,
                    this.httpClient,
                    monitoringAccount.Name,
                    operation,
                    serializedContent: serializedTargetAccounts).ConfigureAwait(false);

                result.ConfigurationUpdateResults =
                    JsonConvert.DeserializeObject<ConfigurationUpdateResult[]>(
                        response.Item1,
                        this.serializerSettings);

                foreach (var updateResult in result.ConfigurationUpdateResults)
                {
                    if (!updateResult.Success)
                    {
                        result.Success = false;
                        result.ExceptionMessage = updateResult.Message;
                        return result;
                    }
                }

                result.Success = true;
                return result;
            }
            catch (MetricsClientException mce)
            {
                result.Success = false;

                if (mce.ResponseStatusCode == HttpStatusCode.Unauthorized || mce.ResponseStatusCode == HttpStatusCode.Forbidden)
                {
                    var exMsg =
                        $"Unable to sync configuration for monitoringAccount:{monitoringAccount.Name}, metricNamespace:{metricNamespace}, metricName:{metricName}"
                        + $"doesn't have permission to update configurations in mirror accounts. Response:{mce.Message}";

                    throw new ConfigurationValidationException(exMsg, ValidationType.ServerSide, mce);
                }

                result.ExceptionMessage = mce.Message;

                return result;
            }
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            bool skipVersionCheck = false,
            bool validate = true)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrEmpty(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            var metricNames = await this.metricReader.GetMetricNamesAsync(
                monitoringAccount.Name,
                metricNamespace).ConfigureAwait(false);
            var taskList = new List<Task<ConfigurationUpdateResultList>>(this.MaxParallelRunningTasks);

            List<ConfigurationUpdateResultList> results = new List<ConfigurationUpdateResultList>();
            foreach (var metricName in metricNames)
            {
                if (taskList.Count == this.MaxParallelRunningTasks)
                {
                    await this.WaitForSync(taskList, results).ConfigureAwait(false);
                    taskList.Clear();
                }
                else
                {
                    taskList.Add(this.SyncConfigurationAsync(monitoringAccount, metricNamespace, metricName, skipVersionCheck, validate));
                }
            }

            if (taskList.Count > 0)
            {
                await this.WaitForSync(taskList, results).ConfigureAwait(false);
                taskList.Clear();
            }

            return results;
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false,
            bool validate = true)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var namespaces =
                await this.metricReader.GetNamespacesAsync(monitoringAccount.Name).ConfigureAwait(false);

            List<ConfigurationUpdateResultList> results = new List<ConfigurationUpdateResultList>();
            foreach (var ns in namespaces)
            {
                var namespaceResults = await this.SyncConfigurationAsync(
                    monitoringAccount,
                    ns,
                    skipVersionCheck,
                    validate).ConfigureAwait(false);

                // For QOS namespace or other internal namespace, there is no configuration to
                // replicate and thus the namespace results is an empty list.
                if (namespaceResults.Count > 0)
                {
                    results.AddRange(namespaceResults);
                }
            }

            return results;
        }

        /// <summary>
        /// A helper method which waits for all given sync all tasks to complete.
        /// </summary>
        /// <param name="taskList">The task list.</param>
        /// <param name="results">The results.</param>
        /// <returns>An awaitable <see cref="Task" />.</returns>
        private async Task WaitForSync(
            List<Task<ConfigurationUpdateResultList>> taskList,
            List<ConfigurationUpdateResultList> results)
        {
            try
            {
                await Task.WhenAll(taskList).ConfigureAwait(false);
                foreach (var task in taskList)
                {
                    if (task.Result.Success)
                    {
                        if (task.Result.ConfigurationUpdateResults.Count > 0)
                        {
                            results.Add(task.Result);
                        }
                    }
                    else
                    {
                        if (task.Result.ExceptionMessage.Contains(
                            "Monitor configuration to be updated can't be null."))
                        {
                            // No configuration exist for the specified metrics and hence nothing
                            // to replicate.
                            continue;
                        }

                        results.Add(task.Result);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(
                    LoggerLevel.Error,
                    LogId,
                    LogTag,
                    $"Exception occurred while replicating configuration. Exception: {ex}");
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ILocalRawMetric.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// An interface representing data for one raw metric.
    /// </summary>
    public interface ILocalRawMetric
    {
        /// <summary>
        /// Gets the monitoring account to which this metric is reported.
        /// </summary>
        string MonitoringAccount { get; }

        /// <summary>
        /// Gets the metric namespace.
        /// </summary>
        string MetricNamespace { get; }

        /// <summary>
        /// Gets the metric name.
        /// </summary>
        string MetricName { get; }

        /// <summary>
        /// Gets the metric timestamp.
        /// </summary>
        DateTime MetricTimeUtc { get; }

        /// <summary>
        /// Gets the metric dimensions.
        /// </summary>
        IDictionary<string, string> Dimensions { get; }

        /// <summary>
        /// Gets a value indicating whether the metric is a platform metric.
        /// In such case its value should be taken using property <see cref="MetricDoubleValue" />.
        /// </summary>
        bool IsPlatformMetric { get; }

        /// <summary>
        /// Gets the metric value emitted using metric API.
        /// </summary>
        ulong MetricLongValue { get; }

        /// <summary>
        /// Gets the value of the platform counters.
        /// </summary>
        double MetricDoubleValue { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IEtlSubscriber.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
// Defines the subscribers of ETL files being periodically cut and process each one of them.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;

    /// <summary>
    /// Enumeration with the possible status at the end of the processing of a single ETL file.
    /// </summary>
    internal enum EtlCompletionStatus
    {
        /// <summary>
        /// ETL file was successfully processed.
        /// </summary>
        Success,

        /// <summary>
        /// Cancellation was requested while processing the ETL file.
        /// </summary>
        CancellationRequested,

        /// <summary>
        /// The ETL file could not being opened.
        /// </summary>
        FailureToOpen,

        /// <summary>
        /// An exception happened while processing the ETL file.
        /// </summary>
        ErrorWhileProcessing,
    }

    /// <summary>
    /// Defines the subscribers of ETL files being periodically cut and process each one of them.
    /// </summary>
    /// <remarks>
    /// The subscriber is going to receive the raw callbacks provided by ETW to process ETW sessions
    /// (RecordCallback and BufferCallback).
    /// </remarks>
    internal unsafe interface IEtlSubscriber
    {
        /// <summary>
        /// Gets the configuration needed to processes backlog of ETL files.
        /// </summary>
        EtlBacklogConfig EtlBacklogConfig { get; }

        /// <summary>
        /// Gets the minimum interval that the dispatcher should wait between dispatching files to the
        /// subscriber.
        /// </summary>
        TimeSpan MinimumIntervalBetweenEtlFiles { get; }

        /// <summary>
        /// Called when the processing of an ETL file is about to start.
        /// </summary>
        /// <param name="etlFileName">
        /// Name of the ETL file that is about to be processed.
        /// </param>
        /// <remarks>
        /// The subscriber should be processing a single ETL file at time, calls to the ETW callbacks
        /// (see below) will only start after this method is called for the ETL file.
        /// </remarks>
        void Start(string etlFileName);

        /// <summary>
        /// Receives the ETW callback passing the ETW events from the ETL file currently being processed,
        /// see EventRecordCallback on MSDN for more information.
        /// </summary>
        /// <param name="eventRecord">
        /// Pointer to the event record received from the ETW session.
        /// </param>
        void RecordCallback(NativeMethods.EventRecord* eventRecord);

        /// <summary>
        /// Receives the ETW callback passing the ETW buffers from the ETL file currently being processed,
        /// see EventTraceBufferCallback on MSDN for more information.
        /// </summary>
        /// <param name="eventTraceLog">
        /// Pointer to the event trace log structure but due to the nature of this structure (variable size
        /// and containing null terminated strings) it is easier to perform marshaling in the code (if needed).
        /// </param>
        /// <returns>
        /// True if the processing of the trace should continue, false to stop processing the trace.
        /// </returns>
        bool BufferCallback(IntPtr eventTraceLog);

        /// <summary>
        /// Notifies that the processing for the given ETL file is complete and let the subscriber knows
        /// if the processed stop because cancellation or not.
        /// </summary>
        /// <param name="etlFileName">
        /// Name of the ETL file that finished being processed.
        /// </param>
        /// <param name="completionStatus">
        /// Enumeration that gives information about the completion status, i.e.: error, success, etc.
        /// </param>
        /// <param name="exception">
        /// In case of error during the processing this will have the exception that was captured.
        /// Use this in combination with the completion status to fully diagnostic what happened.
        /// </param>
        void End(string etlFileName, EtlCompletionStatus completionStatus, Exception exception);
    }
}<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="ConfigFileValidator.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System.Linq;
    using Microsoft.Cloud.Metrics.Client.Configuration;
    using Microsoft.Cloud.Metrics.Client.Logging;

    /// <summary>
    /// The validator class for configuration files.
    /// </summary>
    internal static class ConfigFileValidator
    {
        private static readonly object LogId = Logger.CreateCustomLogId("ConfigFileValidator");

        /// <summary>
        /// Validates the raw metric configuration from file to see if they contain the expected configuration for the command in question.
        /// </summary>
        /// <param name="metricConfigFromFile">The metric configuration from file.</param>
        /// <returns>True if passing validation; false otherwise.</returns>
        internal static bool ValidateRawMetricConfigFromFile(IRawMetricConfiguration metricConfigFromFile)
        {
            if (metricConfigFromFile.Preaggregations == null || !metricConfigFromFile.Preaggregations.Any())
            {
                Logger.Log(LoggerLevel.Error, LogId, "ValidateRawMetricConfigFromFile", "Preaggregations property is not set or empty so it seems to be an invalid raw metric config.");

                return false;
            }

            return true;
        }

        /// <summary>
        /// Validates the composite metric configuration from file to see if they contain the expected configuration for the command in question.
        /// </summary>
        /// <param name="metricConfigFromFile">The metric configuration from file.</param>
        /// <returns>True if passing validation; false otherwise.</returns>
        internal static bool ValidateCompositeMetricConfigFromFile(ICompositeMetricConfiguration metricConfigFromFile)
        {
            if (metricConfigFromFile.MetricSources == null || !metricConfigFromFile.MetricSources.Any())
            {
                Logger.Log(LoggerLevel.Error, LogId, "ValidateCompositeMetricConfigFromFile", "MetricSources property is not set or empty so it seems to be an invalid composite metric config.");

                return false;
            }

            return true;
        }

        /// <summary>
        /// Validates the metric configuration from file to see if they contain the expected configuration contents.
        /// </summary>
        /// <param name="metricConfigFromFile">The metric configuration from file.</param>
        /// <returns>True if passing validation; false otherwise.</returns>
        internal static bool ValidateMetricConfigFromFile(IMetricConfiguration metricConfigFromFile)
        {
            return metricConfigFromFile is RawMetricConfiguration
                ? ValidateRawMetricConfigFromFile((RawMetricConfiguration)metricConfigFromFile)
                : ValidateCompositeMetricConfigFromFile((CompositeMetricConfiguration)metricConfigFromFile);
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MonitorIdentifier.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Monitors
{
    using System;

    using Microsoft.Online.Metrics.Serialization.Configuration;

    using Newtonsoft.Json;

    /// <summary>
    /// A class representing a monitor identifier.
    /// </summary>
    public struct MonitorIdentifier : IEquatable<MonitorIdentifier>
    {
        /// <summary>
        /// The metric identifier.
        /// </summary>
        private readonly MetricIdentifier metricIdentifier;

        /// <summary>
        /// The monitor ID as in the monitor configuration.
        /// </summary>
        private readonly string monitorId;

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitorIdentifier"/> struct.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <param name="monitorId">The monitor identifier.</param>
        [JsonConstructor]
        public MonitorIdentifier(MetricIdentifier metricIdentifier, string monitorId)
        {
            if (monitorId == null)
            {
                throw new ArgumentNullException("monitorId");
            }

            this.metricIdentifier = metricIdentifier;
            this.monitorId = monitorId;
        }

        /// <summary>
        /// Gets the metric identifier.
        /// </summary>
        public MetricIdentifier MetricIdentifier
        {
            get
            {
                return this.metricIdentifier;
            }
        }

        /// <summary>
        /// Gets the monitor ID as in the monitor configuration.
        /// </summary>
        public string MonitorId
        {
            get
            {
                return this.monitorId;
            }
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
        /// </returns>
        public bool Equals(MonitorIdentifier other)
        {
            return this.metricIdentifier.Equals(other.metricIdentifier)
                   && string.Equals(this.monitorId, other.monitorId, StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Determines whether the specified <paramref name="obj"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            return obj is MonitorIdentifier && this.Equals((MonitorIdentifier)obj);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                return (this.metricIdentifier.GetHashCode() * 397)
                       ^ (this.monitorId != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(this.monitorId) : 0);
            }
        }

        /// <summary>
        /// Validates this instance.
        /// </summary>
        internal void Validate()
        {
            // ReSharper disable once ImpureMethodCallOnReadonlyValueField
            this.metricIdentifier.Validate();

            if (string.IsNullOrWhiteSpace(this.monitorId))
            {
                throw new ArgumentException("monitorId is null or empty.");
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HyperLogLogSketches.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Represents a List of Distinct Count dimension name and HyperLogLogSketch associated with it.
    /// </summary>
    public class HyperLogLogSketches : List<KeyValuePair<string, HyperLogLogSketch>>, IReadOnlyHyperLogLogSketches
    {
        /// <summary>
        /// Constucts an instance of class HyperLogLogSketches
        /// </summary>
        public uint HyperLogLogSketchesCount
        {
            get
            {
                return (uint)this.Count;
            }
        }

        /// <summary>
        /// Implements the interface IReadOnlyHyperLogLogSketches
        /// </summary>
        IEnumerable<KeyValuePair<string, HyperLogLogSketch>> IReadOnlyHyperLogLogSketches.HyperLogLogSketches
        {
            get
            {
                return this;
            }
        }

        /// <summary>
        /// Sets the register value for given key.
        /// </summary>
        /// <param name="key">Index value.</param>
        /// <returns>A <see cref="HyperLogLogSketch"/> instance at the given index.</returns>
        public HyperLogLogSketch this[string key]
        {
            get
            {
                var index = this.Find(key);
                if (index < 0)
                {
                    throw new KeyNotFoundException(string.Format("{0} not found in sketches list", key));
                }

                return this[index].Value;
            }

            set
            {
                this.Add(key, value);
            }
        }

        /// <summary>
        /// Checks if given distinct count dimension exists.
        /// </summary>
        /// <param name="distinctCountDimensionName">Distinct count dimension name.</param>
        /// <returns>True if exists else false.</returns>
        public bool ContainsKey(string distinctCountDimensionName)
        {
            return this.Find(distinctCountDimensionName) >= 0;
        }

        /// <summary>
        /// Adds a key/value pair to <see cref="HyperLogLogSketches"/> by using the specified function, if the key does not already exist.
        /// </summary>
        /// <param name="key">Key of the element.</param>
        /// <param name="valueFactory">The function used to generate <see cref="HyperLogLogSketch"/>.</param>
        /// <returns><see cref="HyperLogLogSketch"/> for specified key. This will be either the existing value or the new value returned by valueFactory (if specified key is not found).</returns>
        public HyperLogLogSketch GetOrAdd(string key, Func<HyperLogLogSketch> valueFactory)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (valueFactory == null)
            {
                throw new ArgumentNullException(nameof(valueFactory));
            }

            var index = this.Find(key);
            HyperLogLogSketch hyperLogLogSketch;

            if (index >= 0)
            {
                hyperLogLogSketch = this[index].Value;
            }
            else
            {
                hyperLogLogSketch = valueFactory();
                this.Add(new KeyValuePair<string, HyperLogLogSketch>(key, hyperLogLogSketch));
            }

            return hyperLogLogSketch;
        }

        /// <summary>
        /// Adds the given distinct count dimension and sketch to the list.
        /// </summary>
        /// <param name="distinctCountDimensionName">Distinct count dimension name.</param>
        /// <param name="sketch">Sketch data.</param>
        public void Add(string distinctCountDimensionName, HyperLogLogSketch sketch)
        {
            var index = this.Find(distinctCountDimensionName);

            if (index >= 0)
            {
                this[index] = new KeyValuePair<string, HyperLogLogSketch>(distinctCountDimensionName, sketch);
            }
            else
            {
                this.Add(new KeyValuePair<string, HyperLogLogSketch>(distinctCountDimensionName, sketch));
            }
        }

        private int Find(string key)
        {
            int index = -1;

            for (int i = 0; i < this.Count; i++)
            {
                if (this[i].Key.Equals(key, System.StringComparison.OrdinalIgnoreCase))
                {
                    index = i;
                    break;
                }
            }

            return index;
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="FrontEndMetricDeserializer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;

    /// <summary>
    /// Class which deserializes Autopilot count (metric) data.
    /// Serialization format corresponds to one described in http://sharepoint/sites/autopilot/team/Docs/Silicon/Monitroing%20Team/AD%20Metrics%20%20Autopilot%20Counter%20Data%20Common%20Serialization%20Format.docx.
    /// </summary>
    /// <typeparam name="TMetadata">Type of metadata object used for deserialization.</typeparam>
    public sealed class FrontEndMetricDeserializer<TMetadata>
        where TMetadata : IMetricMetadata
    {
        /// <summary>
        /// TDigest format prefix value
        /// </summary>
        public const uint TDigestPrefixValue = 0x74; // == 't' "tDigest"

        private const ushort MinVersion = 3;
        private const ushort MaxVersion = 6;
        private const uint TypeSerializerFlags = 0x12020000; // Corresponds to 0001.001.0000.0001.00000000000000000 (Use string and metadata interning with variable-length integer serialization)
        private readonly List<string> stringsDictionary = new List<string>();
        private readonly List<TMetadata> metadataDictionary = new List<TMetadata>();
        private readonly List<KeyValuePair<ulong, uint>> histogramBuffer = new List<KeyValuePair<ulong, uint>>(2000);
        private readonly List<string> reusableStringsList = new List<string>();

        /// <summary>
        /// Validates the data packet by CRC check
        /// </summary>
        /// <param name="dataPacket">Data packet to check.</param>
        /// <exception cref="CrcCheckFailedSerializationException">
        /// Throws when CRC check fails.
        /// </exception>
        public static void ValidateCrc(byte[] dataPacket)
        {
            var version = (ushort)(dataPacket[0] | dataPacket[1] << 8);

            if (version < 5)
            {
                // No CRC is added for versions less than 5.
                return;
            }

            var crc = (uint)(dataPacket[2] | dataPacket[3] << 8 | dataPacket[4] << 16 | dataPacket[5] << 24);
            var computedCrc = Crc.ComputeCrc(0, dataPacket, 6);

            if (crc != computedCrc)
            {
                throw new CrcCheckFailedSerializationException($"Crc check failed. Computed CRC : {crc}, Packet CRC: {computedCrc}");
            }
        }

        /// <summary>
        /// Deserializes counter (metric) data from the stream and adds all objects to provided collection.
        /// </summary>
        /// <param name="stream">Stream from which data should be deserialized. Stream should be readable and provide randon access.</param>
        /// <param name="metricBuilder">An object responsible for creation and further consumption of deserialized data.</param>
        /// <param name="maxMetricStringsLength">Maximum length of strings, which represent metric name, dimension names and values.</param>
        /// <param name="maxMetricNamespaceStringsLength">Maximum length of metric namespace string.</param>
        /// <param name="maxMetricDimensionValueStringsLength">Maximum length of metric dimension value string.</param>
        public void Deserialize(
            Stream stream,
            IFrontEndMetricBuilder<TMetadata> metricBuilder,
            int maxMetricStringsLength,
            int maxMetricNamespaceStringsLength,
            int maxMetricDimensionValueStringsLength)
        {
            if (!stream.CanRead || !stream.CanSeek)
            {
                throw new ArgumentException(@"Stream should be readable and provide random access.", nameof(stream));
            }

            try
            {
                using (var reader = new BinaryReader(stream, Encoding.UTF8, true))
                {
                    var startStreamPosition = stream.Position;

                    // Read version and type serializers info
                    var version = reader.ReadUInt16();
                    if (version < MinVersion || version > MaxVersion)
                    {
                        throw new VersionNotSupportedMetricSerializationException(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "Version is not supported. ReadVersion:{0}, MinVersion:{1}, MaxVersion:{2}.",
                                version,
                                MinVersion,
                                MaxVersion));
                    }

                    if (version >= 5)
                    {
                        // Read CRC. CRC check is done in upper layers.
                        reader.ReadUInt32();
                    }

                    if (reader.ReadUInt32() != TypeSerializerFlags)
                    {
                        throw new VersionNotSupportedMetricSerializationException("Type serializers not supported.");
                    }

                    metricBuilder.SetSerializationVersion(version);

                    // Read strings
                    var deserializerDataPosition = stream.Position;
                    stream.Position += sizeof(long);
                    stream.Position = startStreamPosition + reader.ReadInt64();
                    var count = SerializationUtils.ReadUInt32FromBase128(reader);
                    for (uint i = 0; i < count; ++i)
                    {
                        this.stringsDictionary.Add(metricBuilder.GetString(reader.ReadString()));
                    }

                    var endOfPacketStreamPosition = stream.Position;

                    // Read metrics metadata
                    stream.Position = deserializerDataPosition;
                    stream.Position = startStreamPosition + reader.ReadInt64();
                    count = SerializationUtils.ReadUInt32FromBase128(reader);
                    for (uint i = 0; i < count; ++i)
                    {
                        this.metadataDictionary.Add(this.ReadMetricMetadata(reader, metricBuilder, maxMetricStringsLength, maxMetricNamespaceStringsLength));
                    }

                    // Read metrics data
                    stream.Position = deserializerDataPosition + (2 * sizeof(long));
                    this.ReadMetricsData(reader, metricBuilder, version, maxMetricDimensionValueStringsLength);

                    // Bring back the stream to total read data position
                    stream.Position = endOfPacketStreamPosition;
                }
            }
            catch (IOException ioException)
            {
                throw new MetricSerializationException("Failed to deserialize data. Problem with input stream.", ioException);
            }
            catch (Exception exception)
            {
                var serializationException = exception as MetricSerializationException;
                bool isInvalidData = false;
                if (serializationException != null)
                {
                    isInvalidData = serializationException.IsInvalidData;
                }

                throw new MetricSerializationException("Failed to deserialize data. Likely the incoming stream contains corrupted data.", exception, isInvalidData);
            }
            finally
            {
                this.metadataDictionary.Clear();
                this.stringsDictionary.Clear();
                this.histogramBuffer.Clear();
                this.reusableStringsList.Clear();
            }
        }

        /// <summary>
        /// Read metrics count and advances the stream.
        /// </summary>
        /// <param name="stream">Stream of incoming events data for one packet.</param>
        /// <returns>Number of metrics in packet.</returns>
        public int ReadMetricsCount(Stream stream)
        {
            using (var reader = new BinaryReader(stream, Encoding.UTF8))
            {
                var version = reader.ReadUInt16();

                if (version >= 5)
                {
                    reader.ReadUInt32(); // for CRC
                }

                if (reader.ReadUInt32() != TypeSerializerFlags)
                {
                    throw new VersionNotSupportedMetricSerializationException("Type serializers not supported.");
                }

                stream.Position += 2 * sizeof(long); // skipping address of strings and address of metadata

                if (version >= 5)
                {
                    SerializationUtils.ReadInt64FromBase128(reader); // for packet time
                }

                return reader.ReadInt32();
            }
        }

        private void ReadMetricsData(
            BinaryReader reader,
            IFrontEndMetricBuilder<TMetadata> metricBuilder,
            ushort version,
            int maxMetricDimensionValueStringsLength)
        {
            long packetTime = 0;

            if (version >= 5)
            {
                packetTime = (long)SerializationUtils.ReadUInt64FromBase128(reader);
            }

            Stream readerStream = reader.BaseStream;

            var metricsCount = reader.ReadUInt32();
            for (var i = 0; i < metricsCount; ++i)
            {
                DateTime timeUtc;
                var count = 0U;
                var sum = default(MetricValueV2);
                var min = default(MetricValueV2);
                var max = default(MetricValueV2);
                double sumOfSquareDiffFromMean = 0;

                var metadata = this.ReadMetricMetadataByIndex(reader);

                if (version >= 5)
                {
                    var timeInTicks = (packetTime - SerializationUtils.ReadInt64FromBase128(reader)) * SerializationUtils.OneMinuteInterval;
                    timeUtc = new DateTime(timeInTicks, DateTimeKind.Utc);
                }
                else
                {
                    timeUtc = new DateTime((long)SerializationUtils.ReadUInt64FromBase128(reader), DateTimeKind.Utc);
                }

                for (var j = 0; j < metadata.DimensionsCount; ++j)
                {
                    var dimensionValue = this.ReadStringByIndex(reader);
                    if (dimensionValue.Length > maxMetricDimensionValueStringsLength)
                    {
                        throw new MetricSerializationException($"Dimension value string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricDimensionValueStringsLength}, Value:{dimensionValue}.", null, true);
                    }

                    this.reusableStringsList.Add(dimensionValue);
                }

                var samplingTypes = (SamplingTypes)SerializationUtils.ReadUInt32FromBase128(reader);
                var isDouble = (samplingTypes & SamplingTypes.DoubleValueType) != 0;
                var isDoubleStoredAslong = (samplingTypes & SamplingTypes.DoubleValueStoredAsLongType) != 0;

                if ((samplingTypes & SamplingTypes.Min) != 0)
                {
                    min = this.ReadMetricValue(reader, isDouble, isDoubleStoredAslong);
                }

                if ((samplingTypes & SamplingTypes.Max) != 0)
                {
                   max = this.ReadMetricValue(reader, isDouble, isDoubleStoredAslong);
                }

                if ((samplingTypes & SamplingTypes.Sum) != 0)
                {
                    sum = this.ReadMetricValue(reader, isDouble, isDoubleStoredAslong);
                }

                if ((samplingTypes & SamplingTypes.Count) != 0)
                {
                    count = SerializationUtils.ReadUInt32FromBase128(reader);
                }

                if ((samplingTypes & SamplingTypes.SumOfSquareDiffFromMean) != 0)
                {
                    sumOfSquareDiffFromMean = reader.ReadDouble();
                }

                bool haveHistogram = (samplingTypes & SamplingTypes.Histogram) != 0;
                metricBuilder.BeginMetricCreation(metadata, this.reusableStringsList, timeUtc, samplingTypes, count, sum, min, max, sumOfSquareDiffFromMean);
                this.reusableStringsList.Clear();

                if (haveHistogram)
                {
                    IEnumerable<KeyValuePair<ulong, uint>> histogramBuckets =
                        SerializationUtils.ReadHistogram(reader, hasHistogramSizePrefix: version > 3);

                    this.histogramBuffer.Clear();
                    this.histogramBuffer.AddRange(histogramBuckets);
                    metricBuilder.AssignHistogram(this.histogramBuffer);
                }

                if ((samplingTypes & SamplingTypes.HyperLogLogSketch) != 0)
                {
                    var sizeOfHyperLogLogSketches = reader.ReadInt32();
                    metricBuilder.AssignHyperLogLogSketch(reader, sizeOfHyperLogLogSketches);
                }

                if (version >= 6)
                {
                    bool haveTDigest = (samplingTypes & SamplingTypes.TDigest) != 0;
                    bool readTDigest = false;

                    // starting from version 6, there is a list of TLV-type
                    // (https://en.wikipedia.org/wiki/Type-length-value) tuples in the rest of the serialized metric.

                    // deserialize all of them ignoring the unknown ones.
                    // list TLV values is expected to contain a single end-of-list marker in the end with T = 0x00
                    uint type;
                    while ((type = SerializationUtils.ReadUInt32FromBase128(reader)) != 0x00)
                    {
                        int length = (int)SerializationUtils.ReadUInt32FromBase128(reader);
                        long nextPos = readerStream.Position + length;

                        switch (type)
                        {
                            case TDigestPrefixValue: // 0x74 == 't' (tDigest)
                                if (haveTDigest)
                                {
                                    if (!readTDigest)
                                    {
                                        metricBuilder.AssignTDigest(reader, length);
                                        readTDigest = true;
                                    }
                                    else
                                    {
                                        // if we already saw tDigest value and see it
                                        // second time it is a sign of a protocol error.
                                        throw new MetricSerializationException("Saw 2 TDigest values for the same metric", null, isInvalidData: true);
                                    }
                                }
                                else
                                {
                                    // if TLV list contains tDigest but the sampling types does not
                                    // it is a protocol error
                                    throw new MetricSerializationException("Sampling types do not contain tDigest, but TLV list contains it", null, isInvalidData: true);
                                }

                                break;

                            default:
                                // ignore unknown types
                                break;
                        }

                        // always set the position to point to the next entry.
                        // do not trust the deserializer code to leave the position set correctly
                        // this helps prevent compatibility problems and makes the deserializer
                        // more stable
                        readerStream.Position = nextPos;
                    }

                    if (haveTDigest && !readTDigest)
                    {
                        // if sampling types contain TDigest but we have not seen it in TLV this is
                        // a sign of protocol error
                        throw new MetricSerializationException("Sampling types contain tDigest, but TLV list does not contain it", null, isInvalidData: true);
                    }
                }

                metricBuilder.EndMetricCreation();
            }
        }

        private string ReadStringByIndex(BinaryReader reader)
        {
            var index = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            return this.stringsDictionary[index];
        }

        private TMetadata ReadMetricMetadataByIndex(BinaryReader reader)
        {
            var index = (int)SerializationUtils.ReadUInt32FromBase128(reader);
            return this.metadataDictionary[index];
        }

        private TMetadata ReadMetricMetadata(BinaryReader reader, IFrontEndMetricBuilder<TMetadata> metricBuilder, int maxMetricStringsLength, int maxMetricNamespaceStringsLength)
        {
            var metricNamespace = this.ReadStringByIndex(reader);
            if (metricNamespace.Length > maxMetricNamespaceStringsLength)
            {
                throw new MetricSerializationException(
                    $"Namespace string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricNamespaceStringsLength}, Value:{metricNamespace}.", null, true);
            }

            var metricName = this.ReadStringByIndex(reader);
            if (metricName.Length > maxMetricStringsLength)
            {
                throw new MetricSerializationException(
                    $"Metric name string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricStringsLength}, Value:{metricName}.", null, true);
            }

            var count = SerializationUtils.ReadUInt32FromBase128(reader);
            for (var i = 0; i < count; ++i)
            {
                var dimensionName = this.ReadStringByIndex(reader);
                if (dimensionName.Length > maxMetricStringsLength)
                {
                    throw new MetricSerializationException(
                        $"Dimension name string in the packet exceeds preconfigured length. Packet is corrupted. MaxLength:{maxMetricStringsLength}, Value:{dimensionName}.", null, true);
                }

                this.reusableStringsList.Add(dimensionName);
            }

            var result = metricBuilder.CreateMetadata(metricNamespace, metricName, this.reusableStringsList);
            this.reusableStringsList.Clear();
            return result;
        }

        private MetricValueV2 ReadMetricValue(BinaryReader reader, bool isDouble, bool isDoubleStoredAsLong)
        {
            if (isDouble)
            {
                if (isDoubleStoredAsLong)
                {
                    return new MetricValueV2 { ValueAsDouble = SerializationUtils.ReadInt64FromBase128(reader) };
                }
                else
                {
                    return new MetricValueV2 { ValueAsDouble = reader.ReadDouble() };
                }
            }
            else
            {
                return new MetricValueV2 { ValueAsULong = SerializationUtils.ReadUInt64FromBase128(reader) };
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IMonitoringAccount.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// This object represents one MDM Monitoring Account, which is used to grant permission
    /// to users, certificates, and groups.
    /// </summary>
    public interface IMonitoringAccount
    {
        /// <summary>
        /// The name of the monitoring account.
        /// </summary>
        string Name { get; }

        /// <summary>
        /// The display name of the monitoring account.
        /// </summary>
        string DisplayName { get; set; }

        /// <summary>
        /// The description of the monitoring account.
        /// </summary>
        string Description { get; set; }

        /// <summary>
        /// The host name of the MDM stamp that currently owns this account.
        /// </summary>
        string HomeStampHostName { get; }

        /// <summary>
        /// The list of entities that have access to this MDM account and their roles.
        /// </summary>
        IEnumerable<IPermissionV2> Permissions { get; }

        /// <summary>
        /// The time the account was last updated.
        /// </summary>
        DateTime LastUpdatedTimeUtc { get; }

        /// <summary>
        /// The identity that updated the account most recently.
        /// </summary>
        string LastUpdatedBy { get; }

        /// <summary>
        /// The version of the monitoring account configuration.
        /// </summary>
        uint Version { get; }

        /// <summary>
        /// Gets the list of mirror monitoring accounts.
        /// </summary>
        IEnumerable<string> MirrorMonitoringAccountList { get; }

        /// <summary>
        /// Adds a permission to the account.
        /// </summary>
        /// <param name="permission">Permission to add.</param>
        void AddPermission(IPermissionV2 permission);

        /// <summary>
        /// Remove permission from the account.
        /// </summary>
        /// <param name="permission">Permission to remove.</param>
        void RemovePermission(IPermissionV2 permission);

        /// <summary>
        /// Adds a monitoring account to the mirror monitoring account list.
        /// </summary>
        /// <param name="monitoringAccountName">Name of the monitoring account.</param>
        void AddMirrorMonitoringAccount(string monitoringAccountName);

        /// <summary>
        /// Removes a monitoring account from the mirror monitoring account list.
        /// </summary>
        /// <param name="monitoringAccountName">Name of the monitoring account.</param>
        void RemoveMirrorMonitoringAccount(string monitoringAccountName);
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricDefinitionV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Represents a metric definition.
    /// A metric definition uniquely identifies a metric name, set of dimensions emitted for this metric by a client.
    /// </summary>
    public sealed class MetricDefinitionV2 : IEquatable<MetricDefinitionV2>
    {
        private int hashCode;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricDefinitionV2"/> class.
        /// </summary>
        /// <param name="monitoringAccount">Metric monitoring account.</param>
        /// <param name="metricNamespace">Metric namespace name.</param>
        /// <param name="metricName">Metric name.</param>
        /// <param name="dimensionNames">Dimension names enumeration for the metric definition.</param>
        public MetricDefinitionV2(string monitoringAccount, string metricNamespace, string metricName, IEnumerable<string> dimensionNames)
        {
            this.MonitoringAccount = monitoringAccount;
            this.MetricNamespace = metricNamespace;
            this.MetricName = metricName;

            this.DimensionNames = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
            foreach (var dimensionName in dimensionNames)
            {
                this.DimensionNames.Add(dimensionName);
            }

            this.hashCode = 524287;

            var temp = StringComparer.OrdinalIgnoreCase.GetHashCode(monitoringAccount);
            this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp))) ^ temp;
            this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp)));

            temp = StringComparer.OrdinalIgnoreCase.GetHashCode(metricNamespace);
            this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp))) ^ temp;
            this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp)));

            temp = StringComparer.OrdinalIgnoreCase.GetHashCode(metricName);
            this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp))) ^ temp;
            this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp)));

            foreach (var currentDimensionValue in this.DimensionNames)
            {
                if (string.IsNullOrWhiteSpace(currentDimensionValue))
                {
                    throw new ArgumentException("Dimension names cannot be null or empty strings.", nameof(dimensionNames));
                }

                temp = StringComparer.OrdinalIgnoreCase.GetHashCode(currentDimensionValue);
                this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp))) ^ temp;
                this.hashCode = (int)(((uint)this.hashCode << temp) | ((uint)this.hashCode >> (32 - temp)));
            }
        }

        /// <summary>
        /// The name of the monitoring account the metric belongs too.
        /// </summary>
        public string MonitoringAccount { get; private set; }

        /// <summary>
        /// The name of the namespace the metric belongs too.
        /// </summary>
        public string MetricNamespace { get; private set; }

        /// <summary>
        /// The metric name for current metric definition.
        /// </summary>
        public string MetricName { get; private set; }

        /// <summary>
        /// List of dimensions emitted for this metric definition instance.
        /// </summary>
        public SortedSet<string> DimensionNames { get; private set; }

        /// <inheritdoc/>
        public override int GetHashCode()
        {
            return this.hashCode;
        }

        /// <inheritdoc/>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as MetricDefinitionV2);
        }

        /// <inheritdoc/>
        public bool Equals(MetricDefinitionV2 other)
        {
            if (other == null)
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            if (this.hashCode != other.hashCode ||
                this.DimensionNames.Count != other.DimensionNames.Count ||
                !this.MonitoringAccount.Equals(other.MonitoringAccount) ||
                !this.MonitoringAccount.Equals(other.MetricNamespace) ||
                !this.MonitoringAccount.Equals(other.MetricName))
            {
                return false;
            }

            return this.DimensionNames.SetEquals(other.DimensionNames);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DimensionConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Configuration
{
    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;

    /// <summary>
    /// Represents the configuration of a dimension.
    /// </summary>
    public sealed class DimensionConfiguration
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DimensionConfiguration"/> class.
        /// </summary>
        /// <param name="id">The dimension name.</param>
        public DimensionConfiguration(string id)
            : this(id, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DimensionConfiguration"/> class.
        /// </summary>
        /// <param name="id">The dimension name.</param>
        /// <param name="dimensionValuesToIgnore">The dimension values to ignore.</param>
        [JsonConstructor]
        public DimensionConfiguration(string id, IList<string> dimensionValuesToIgnore)
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentException("id is null or empty.");
            }

            this.Id = id;
            this.DimensionValuesToIgnore = dimensionValuesToIgnore;
        }

        /// <summary>
        /// Gets the identifier of this instance.
        /// </summary>
        public string Id { get; private set; }

        /// <summary>
        /// Gets or sets the dimension values to ignore.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public IList<string> DimensionValuesToIgnore { get; set; }

        /// <summary>
        /// Gets or sets the identifier.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public long Identifier { get; set; }

        /// <summary>
        /// Validates the current instance and throws a <see cref="ArgumentException"/> if the instance is invalid.
        /// </summary>
        public void Validate()
        {
            if (string.IsNullOrEmpty(this.Id))
            {
                throw new ArgumentException("Property 'Id' is null or empty.");
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Certificate.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using Newtonsoft.Json;

    /// <summary>
    /// Certificate with access to MDM.
    /// </summary>
    [Obsolete]
    public sealed class Certificate : IPermission
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Certificate"/> class.
        /// Create a new certificate for MDM access.
        /// </summary>
        /// <param name="identity">The thumbprint of the certificate in hexidecimal form.</param>
        /// <param name="role">The role granted to this certificate.</param>
        [JsonConstructor]
        public Certificate(string identity, Role role)
        {
            if (string.IsNullOrWhiteSpace(identity))
            {
                throw new ArgumentNullException(nameof(identity));
            }

            this.Identity = identity;
            this.Role = role;
        }

        /// <summary>
        /// The identity to grant permission.
        /// </summary>
        public string Identity { get; }

        /// <summary>
        /// The level of access to be granted to this identity.
        /// </summary>
        public Role Role { get; set; }
    }
}
<file_sep>using System.Collections.Generic;

namespace AzSignalR.Monitor.JobRegistry
{
    public class SignalRResourceMeta
    {
        public string Subscription { get; set; }
        public string ResourceGroup { get; set; }
        public string ResourceName { get; set; }
        public string KubeId { get; set; }
        public string RowGuid { get; set; }

        public static SignalRResourceMeta FromDict(IDictionary<string, string> labels)
        {
            labels.TryGetValue("subscription", out var subscription);
            labels.TryGetValue("resourceGroup", out var resourceGroup);
            labels.TryGetValue("resourceName", out var resourceName);
            labels.TryGetValue("resourceKubeId", out var kubeId);
            labels.TryGetValue("resourceRowGuid", out var rowGuid);

            if (string.IsNullOrEmpty(subscription)
                && string.IsNullOrEmpty(resourceGroup)
                && string.IsNullOrEmpty(resourceName)
                && string.IsNullOrEmpty(kubeId)
                && string.IsNullOrEmpty(rowGuid))
            {
                return null;
            }

            var meta = new SignalRResourceMeta
            {
                Subscription = subscription,
                ResourceGroup = resourceGroup,
                ResourceName = resourceName,
                KubeId = kubeId,
                RowGuid = rowGuid,
            };
            return meta;
        }
    }
}
<file_sep>﻿// <copyright file="PreaggregateFilters.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

// ReSharper disable once CheckNamespace
namespace Microsoft.Cloud.Metrics.Client.PreaggregateFiltersManagement
{
    using System;
    using System.Collections.Generic;
    using Metrics;
    using Newtonsoft.Json;

    /// <summary>
    /// Represent a set of filters for a single pre-aggregate.
    /// </summary>
    [JsonObject]
    public sealed class PreaggregateFilters
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PreaggregateFilters"/> class.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="preaggregateDimensionNames">The preaggregate dimension names.</param>
        /// <param name="filterValues">The filter values.</param>
        public PreaggregateFilters(
            string monitoringAccount,
            string metricNamespace,
            string metricName,
            IEnumerable<string> preaggregateDimensionNames,
            IReadOnlyList<DimensionFilter> filterValues)
        {
            if (string.IsNullOrEmpty(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrEmpty(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrEmpty(metricName))
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            if (preaggregateDimensionNames == null)
            {
                throw new ArgumentNullException(nameof(preaggregateDimensionNames));
            }

            if (filterValues == null)
            {
                throw new ArgumentNullException(nameof(filterValues));
            }

            if (filterValues.Count == 0)
            {
                throw new ArgumentException($"{nameof(filterValues)} cannot be empty");
            }

            this.MonitoringAccount = monitoringAccount;
            this.MetricNamespace = metricNamespace;
            this.MetricName = metricName;
            var preaggregateDimensionNamesSet = new SortedSet<string>(preaggregateDimensionNames, StringComparer.OrdinalIgnoreCase);

            if (preaggregateDimensionNamesSet.Count == 0)
            {
                throw new ArgumentException($"{nameof(preaggregateDimensionNames)} cannot be empty");
            }

            this.PreaggregateDimensionNames = preaggregateDimensionNamesSet;

            foreach (string dim in this.PreaggregateDimensionNames)
            {
                if (string.IsNullOrWhiteSpace(dim))
                {
                    throw new ArgumentException($"{nameof(preaggregateDimensionNames)} cannot have empty or null values");
                }
            }

            this.DimensionFilters = filterValues;
            var serverRepresentationOfFilterValues = new List<PreaggregateDimensionFilterValues>();
            foreach (DimensionFilter filterValue in filterValues)
            {
                if (filterValue.IsExcludeFilter)
                {
                    throw new ArgumentException($"{nameof(filterValues)} are not allowed to have exclude filters. Dimension Name with exclude filters:{filterValue.DimensionName}");
                }

                serverRepresentationOfFilterValues.Add(new PreaggregateDimensionFilterValues(filterValue.DimensionName, filterValue.DimensionValues));
            }

            this.FilterValues = serverRepresentationOfFilterValues;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PreaggregateFilters"/> class.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="preaggregateDimensionNames">The preaggregate dimension names.</param>
        /// <param name="filterValues">The filter values.</param>
        [JsonConstructor]
        internal PreaggregateFilters(
            string monitoringAccount,
            string metricNamespace,
            string metricName,
            IEnumerable<string> preaggregateDimensionNames,
            IReadOnlyList<PreaggregateDimensionFilterValues> filterValues)
        {
            this.MonitoringAccount = monitoringAccount;
            this.MetricNamespace = metricNamespace;
            this.MetricName = metricName;
            var preaggregateDimensionNamesSet = new SortedSet<string>(preaggregateDimensionNames, StringComparer.OrdinalIgnoreCase);
            this.PreaggregateDimensionNames = preaggregateDimensionNamesSet;
            this.FilterValues = filterValues;

            var dimensionFilters = new List<DimensionFilter>(this.FilterValues.Count);
            foreach (PreaggregateDimensionFilterValues filter in this.FilterValues)
            {
                dimensionFilters.Add(DimensionFilter.CreateIncludeFilter(filter.FilterDimensionName, filter.FilterValues));
            }

            this.DimensionFilters = dimensionFilters;
        }

        /// <summary>
        /// Gets or sets the monitoring account.
        /// </summary>
        public string MonitoringAccount { get; }

        /// <summary>
        /// Gets or sets the metric namespace.
        /// </summary>
        public string MetricNamespace { get; }

        /// <summary>
        /// Gets or sets the name of the metric.
        /// </summary>
        public string MetricName { get; }

        /// <summary>
        /// Gets or sets the preaggregate dimension names.
        /// </summary>
        public IEnumerable<string> PreaggregateDimensionNames { get; }

        /// <summary>
        /// Gets the filter values.
        /// </summary>
        [JsonIgnore]
        public IReadOnlyList<DimensionFilter> DimensionFilters { get; }

        /// <summary>
        /// Gets the filter values representation of server side.
        /// </summary>
        [JsonProperty]
        internal IReadOnlyList<PreaggregateDimensionFilterValues> FilterValues { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QueryResultListV3.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using System.Collections.Generic;

    using Newtonsoft.Json;

    /// <summary>
    /// Holds the list of query results and the associated metadata.
    /// </summary>
    /// <seealso cref="IQueryResultListV3" />
    public sealed class QueryResultListV3 : IQueryResultListV3
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="QueryResultListV3"/> class.
        /// </summary>
        /// <param name="startTimeUtc">The start time.</param>
        /// <param name="endTimeUtc">The end time.</param>
        /// <param name="timeResolutionInMinutes">The time resolution in milliseconds.</param>
        /// <param name="results">The result time series.</param>
        [JsonConstructor]
        internal QueryResultListV3(DateTime startTimeUtc, DateTime endTimeUtc, int timeResolutionInMinutes, IReadOnlyList<IQueryResultV3> results)
        {
            this.StartTimeUtc = startTimeUtc;
            this.EndTimeUtc = endTimeUtc;
            this.TimeResolutionInMinutes = timeResolutionInMinutes;
            this.Results = results;
        }

        /// <summary>
        /// Gets the end time in UTC for the query results.
        /// </summary>
        public DateTime EndTimeUtc { get; }

        /// <summary>
        /// Gets the start time in UTC for the query results.
        /// </summary>
        public DateTime StartTimeUtc { get; }

        /// <summary>
        /// Gets the time resolution in milliseconds for the query results.
        /// </summary>
        public int TimeResolutionInMinutes { get; }

        /// <summary>
        /// Gets the query results. Each result represent a single time series where start time, end time and time resolution
        /// is represented by this object members.
        /// </summary>
        public IReadOnlyList<IQueryResultV3> Results { get; }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="MetricNamespaceConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Configuration
{
    using System;
    using System.Collections.Generic;

    using Newtonsoft.Json;

    /// <summary>
    /// The data structure that the batch API for fetching metric configuration returns per namespace
    /// in the new (Monitoring Agent) pipeline. This is JSON serialized to the clients.
    /// </summary>
    public sealed class MetricNamespaceConfiguration
    {
        private DateTime updatedAtUtc;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricNamespaceConfiguration"/> class.
        /// </summary>
        public MetricNamespaceConfiguration()
        {
            this.MetricConfigurations = new HashSet<MetricConfigurationV2>();
        }

        /// <summary>
        /// Gets or sets the metric namespace.
        /// </summary>
        [JsonProperty(PropertyName = "m")]
        public string MetricNamespace { get; set; }

        /// <summary>
        /// Gets or sets the timestamp in UTC of the last updated metric in this namespace.
        /// </summary>
        [JsonProperty(PropertyName = "u")]
        public DateTime UpdatedAtUtc
        {
            get
            {
                return this.updatedAtUtc;
            }

            set
            {
                // Metrics extension today ignores sub-seconds, so we need to work around it for now.
                // We also always advance this property with the recent configuration cache refresh time
                // to avoid missing configurations, even if no configuration was updated at all.
                this.updatedAtUtc = TruncateToIntegralSeconds(value);
            }
        }

        /// <summary>
        /// Gets or sets the array of metric configurations.
        /// </summary>
        [JsonProperty(PropertyName = "c")]
        public HashSet<MetricConfigurationV2> MetricConfigurations { get; set; }

        /// <summary>
        /// Truncates to integral seconds.
        /// </summary>
        /// <param name="dateTime">The date time.</param>
        /// <returns>
        /// The trucated date time.
        /// </returns>
        private static DateTime TruncateToIntegralSeconds(DateTime dateTime)
        {
            return dateTime.AddTicks(-(dateTime.Ticks % TimeSpan.TicksPerSecond));
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="Preaggregation.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;

    /// <summary>
    /// A grouping of dimensions used to aggregate metric data.
    /// </summary>
    public sealed class Preaggregation : IPreaggregation
    {
        private readonly List<string> dimensions;
        private string name;
        private IMinMaxConfiguration minMaxConfiguration;
        private IPercentileConfiguration percentileConfiguration;
        private IRollupConfiguration rollupConfiguration;
        private IPublicationConfiguration publicationConfiguration;
        private IDistinctCountConfiguration distinctCountConfiguration;
        private IFilteringConfiguration filteringConfiguration;

        /// <summary>
        /// Initializes a new instance of the <see cref="Preaggregation"/> class.
        /// Creates a new preaggregate.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="dimensions">The dimensions.</param>
        /// <param name="minMaxConfiguration">The minimum maximum configuration.</param>
        /// <param name="percentileConfiguration">The percentile configuration.</param>
        /// <param name="rollupConfiguration">The rollup configuration.</param>
        /// <param name="publicationConfiguration">The publication configuration.</param>
        /// <param name="distinctCountConfiguration">The distinct count configuration.</param>
        /// <param name="filteringConfiguration">The filtering configuration.</param>
        [JsonConstructor]
        internal Preaggregation(
            string name,
            IEnumerable<string> dimensions,
            MinMaxConfiguration minMaxConfiguration,
            PercentileConfiguration percentileConfiguration,
            RollupConfiguration rollupConfiguration,
            PublicationConfiguration publicationConfiguration,
            DistinctCountConfiguration distinctCountConfiguration,
            IFilteringConfiguration filteringConfiguration)
        {
            this.Name = name;
            this.dimensions = dimensions.ToList();
            this.minMaxConfiguration = minMaxConfiguration;
            this.percentileConfiguration = percentileConfiguration;
            this.rollupConfiguration = rollupConfiguration;
            this.publicationConfiguration = publicationConfiguration;
            this.distinctCountConfiguration = distinctCountConfiguration;
            this.filteringConfiguration = filteringConfiguration;
        }

        /// <summary>
        /// Gets or sets the name of the preaggregate.
        /// </summary>
        public string Name
        {
            get
            {
                return this.name;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.name = value;
            }
        }

        /// <summary>
        /// Gets the dimensions of the preaggregate in sorted order.
        /// </summary>
        public IEnumerable<string> Dimensions
        {
            get { return this.dimensions; }
        }

        /// <summary>
        /// The min/max sampling type configuration.
        /// </summary>
        public IMinMaxConfiguration MinMaxConfiguration
        {
            get
            {
                return this.minMaxConfiguration;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.minMaxConfiguration = value;
            }
        }

        /// <summary>
        /// The filtering type configuration.
        /// </summary>
        public IFilteringConfiguration FilteringConfiguration
        {
            get
            {
                return this.filteringConfiguration;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.filteringConfiguration = value;
            }
        }

        /// <summary>
        /// The percentile sampling type configuration.
        /// </summary>
        public IPercentileConfiguration PercentileConfiguration
        {
            get
            {
                return this.percentileConfiguration;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.percentileConfiguration = value;
            }
        }

        /// <summary>
        /// The data rollup configuration.
        /// </summary>
        public IRollupConfiguration RollupConfiguration
        {
            get
            {
                return this.rollupConfiguration;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.rollupConfiguration = value;
            }
        }

        /// <summary>
        /// The metric data store configuration.
        /// </summary>
        public IPublicationConfiguration PublicationConfiguration
        {
            get
            {
                return this.publicationConfiguration;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.publicationConfiguration = value;
            }
        }

        /// <summary>
        /// The distinct count sampling type configuration.
        /// </summary>
        public IDistinctCountConfiguration DistinctCountConfiguration
        {
            get
            {
                return this.distinctCountConfiguration;
            }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.distinctCountConfiguration = value;
            }
        }

        /// <summary>
        /// Creates a new preaggregate with defaults for the configuration flags.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="dimensions">The dimensions.</param>
        /// <returns>The created preaggregate.</returns>
        public static Preaggregation CreatePreaggregation(string name, IEnumerable<string> dimensions)
        {
            return CreatePreaggregationImpl(
                name,
                dimensions,
                Configuration.MinMaxConfiguration.MinMaxDisabled,
                Configuration.PercentileConfiguration.PercentileDisabled,
                Configuration.RollupConfiguration.RollupDisabled,
                Configuration.PublicationConfiguration.CacheServer,
                new DistinctCountConfiguration(),
                Configuration.FilteringConfiguration.FilteringDisabled);
        }

        /// <summary>
        /// Creates a new filtered metrics store preaggregate with defaults for the configuration flags.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="dimensions">The dimensions.</param>
        /// <param name="minMaxConfiguration">The minimum maximum configuration. Default value is Configuration.MinMaxConfiguration.MinMaxDisabled.</param>
        /// <param name="percentileConfiguration">The percentile configuration. Default value is Configuration.PercentileConfiguration.PercentileDisabled.</param>
        /// <param name="rollupConfiguration">The rollup configuration. Default value is Configuration.RollupConfiguration.RollupDisabled.</param>
        /// <param name="metricStoreConfiguration">The metric store configuration. Default value is Configuration.PublicationConfiguration.AggregatedMetricsStore.</param>
        /// <param name="distinctCountConfiguration">The distinct count configuration. Default value is new DistinctCountConfiguration().</param>
        /// <param name="filteringConfiguration">The filtering configuration. Default value is Configuration.FilteringConfiguration.FilteringDisabled.</param>
        /// <returns>The created preaggregate.</returns>
        public static Preaggregation CreatePreaggregationWithDefaults(
            string name,
            IEnumerable<string> dimensions,
            MinMaxConfiguration minMaxConfiguration = null,
            PercentileConfiguration percentileConfiguration = null,
            RollupConfiguration rollupConfiguration = null,
            PublicationConfiguration metricStoreConfiguration = null,
            DistinctCountConfiguration distinctCountConfiguration = null,
            IFilteringConfiguration filteringConfiguration = null)
        {
            return CreatePreaggregationImpl(
                name,
                dimensions,
                minMaxConfiguration ?? Configuration.MinMaxConfiguration.MinMaxDisabled,
                percentileConfiguration ?? Configuration.PercentileConfiguration.PercentileDisabled,
                rollupConfiguration ?? Configuration.RollupConfiguration.RollupDisabled,
                metricStoreConfiguration ?? (distinctCountConfiguration?.Dimensions.Any() == true ? Configuration.PublicationConfiguration.CacheServer : Configuration.PublicationConfiguration.MetricStore),
                distinctCountConfiguration ?? new DistinctCountConfiguration(),
                filteringConfiguration ?? Configuration.FilteringConfiguration.FilteringDisabled);
        }

        /// <summary>
        /// Creates a new preaggregate.
        /// </summary>
        /// <remarks>
        /// This is older legacy api for backward compatibility. Add new properties to CreatePreaggregationWithDefaultConfiguration instead.
        /// </remarks>
        /// <param name="name">The name.</param>
        /// <param name="dimensions">The dimensions.</param>
        /// <param name="minMaxConfiguration">The minimum maximum configuration.</param>
        /// <param name="percentileConfiguration">The percentile configuration.</param>
        /// <param name="rollupConfiguration">The rollup configuration.</param>
        /// <param name="metricStoreConfiguration">The metric store configuration.</param>
        /// <param name="distinctCountConfiguration">The distinct count configuration.</param>
        /// <returns>The created preaggregate.</returns>
        [Obsolete("CreatePreaggregation is deprecated, please use CreatePreaggregationWithDefaults instead.")]
        public static Preaggregation CreatePreaggregation(
            string name,
            IEnumerable<string> dimensions,
            MinMaxConfiguration minMaxConfiguration,
            PercentileConfiguration percentileConfiguration,
            RollupConfiguration rollupConfiguration,
            PublicationConfiguration metricStoreConfiguration,
            DistinctCountConfiguration distinctCountConfiguration)
        {
            return CreatePreaggregationImpl(
                name,
                dimensions,
                minMaxConfiguration,
                percentileConfiguration,
                rollupConfiguration,
                metricStoreConfiguration,
                distinctCountConfiguration,
                Configuration.FilteringConfiguration.FilteringDisabled);
        }

        /// <summary>
        /// Adds the dimension to the preaggregate.
        /// </summary>
        /// <param name="dimensionToAdd">Name of the dimension to add.</param>
        public void AddDimension(string dimensionToAdd)
        {
            if (string.IsNullOrWhiteSpace(dimensionToAdd))
            {
                throw new ArgumentNullException(nameof(dimensionToAdd));
            }

            var index = 0;
            for (; index < this.dimensions.Count; ++index)
            {
                var comparison = string.Compare(this.dimensions[index], dimensionToAdd, StringComparison.OrdinalIgnoreCase);
                if (comparison == 0)
                {
                    throw new ConfigurationValidationException("Cannot add duplicate dimensions.", ValidationType.DuplicateDimension);
                }

                if (comparison > 0)
                {
                    break;
                }
            }

            if ((index + 1) == this.dimensions.Count)
            {
                this.dimensions.Add(dimensionToAdd);
            }
            else
            {
                this.dimensions.Insert(index, dimensionToAdd);
            }
        }

        /// <summary>
        /// Removes the dimension from the preaggregate.
        /// </summary>
        /// <param name="dimensionName">Name of the dimension.</param>
        public void RemoveDimension(string dimensionName)
        {
            this.dimensions.RemoveAll(x => string.Equals(x, dimensionName, StringComparison.OrdinalIgnoreCase));
        }

        /// <summary>
        /// Creates a new preaggregate.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="dimensions">The dimensions.</param>
        /// <param name="minMaxConfiguration">The minimum maximum configuration.</param>
        /// <param name="percentileConfiguration">The percentile configuration.</param>
        /// <param name="rollupConfiguration">The rollup configuration.</param>
        /// <param name="metricStoreConfiguration">The metric store configuration.</param>
        /// <param name="distinctCountConfiguration">The distinct count configuration.</param>
        /// <param name="filteringConfiguration">The filtering configuration.</param>
        /// <returns>The created preaggregate.</returns>
        private static Preaggregation CreatePreaggregationImpl(
            string name,
            IEnumerable<string> dimensions,
            MinMaxConfiguration minMaxConfiguration,
            PercentileConfiguration percentileConfiguration,
            RollupConfiguration rollupConfiguration,
            PublicationConfiguration metricStoreConfiguration,
            DistinctCountConfiguration distinctCountConfiguration,
            IFilteringConfiguration filteringConfiguration)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }

            if (dimensions == null)
            {
                throw new ArgumentNullException(nameof(dimensions));
            }

            var dimensionList = dimensions.ToList();
            dimensionList.Sort(StringComparer.OrdinalIgnoreCase);
            for (var i = 0; i < (dimensionList.Count - 1); i++)
            {
                if (string.Equals(dimensionList[i], dimensionList[i + 1], StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException("Cannot create a preaggregate with duplicate dimensions.");
                }
            }

            if (minMaxConfiguration == null)
            {
                throw new ArgumentNullException(nameof(minMaxConfiguration));
            }

            if (percentileConfiguration == null)
            {
                throw new ArgumentNullException(nameof(percentileConfiguration));
            }

            if (rollupConfiguration == null)
            {
                throw new ArgumentNullException(nameof(rollupConfiguration));
            }

            if (metricStoreConfiguration == null)
            {
                throw new ArgumentNullException(nameof(metricStoreConfiguration));
            }

            if (distinctCountConfiguration == null)
            {
                throw new ArgumentNullException(nameof(distinctCountConfiguration));
            }

            if (filteringConfiguration == null)
            {
                throw new ArgumentNullException(nameof(filteringConfiguration));
            }

            return new Preaggregation(
                name,
                dimensionList,
                minMaxConfiguration,
                percentileConfiguration,
                rollupConfiguration,
                metricStoreConfiguration,
                distinctCountConfiguration,
                filteringConfiguration);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UnixEpochHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;

    /// <summary>
    /// Methods and values related to time.
    /// </summary>
    /// <remarks>
    /// The formal definition of unix time is the # of seconds that have elapsed since UTC
    /// DateTime(1970, 1, 1, 0, 0, 0). The metrics system refers to unix time as the number of milliseconds
    /// that have elapsed since UTC DateTime(1970, 1, 1, 0, 0, 0).
    /// </remarks>
    internal static class UnixEpochHelper
    {
        /// <summary>
        /// The number of ticks in a millisecond.
        /// </summary>
        internal const long TicksPerMillisecond = 10000;

        /// <summary>
        /// The Unix epoch (January 1, 1970)
        /// </summary>
        internal static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0);

        /// <summary>
        /// The milliseconds between the Unix epoch (January 1, 1970) and the Microsoft epoch (January 1, 0001).
        /// </summary>
        private static readonly long UnixEpochMsEpochDeltaMillis = UnixEpoch.Ticks / TicksPerMillisecond;

        /// <summary>
        /// Gets the Unix time for the given <paramref name="utcTime"/>.
        /// </summary>
        /// <param name="utcTime">A given UTC time</param>
        /// <returns>The Unix time for the given <paramref name="utcTime"/></returns>
        internal static long GetMillis(DateTime utcTime)
        {
            return (utcTime.Ticks / TicksPerMillisecond) - UnixEpochMsEpochDeltaMillis;
        }

        /// <summary>
        /// Gets the DateTime given the Unix time
        /// </summary>
        /// <param name="millis">The Unix time</param>
        /// <returns>The DateTime for the Unix time</returns>
        internal static DateTime FromMillis(long millis)
        {
            return new DateTime(TicksPerMillisecond * (millis + UnixEpochMsEpochDeltaMillis), DateTimeKind.Utc);
        }
    }
}
<file_sep>﻿// <copyright file="PreaggregateDimensionFilterValues.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

// ReSharper disable once CheckNamespace
namespace Microsoft.Cloud.Metrics.Client.PreaggregateFiltersManagement
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;

    /// <summary>
    /// Represents filter values for a single preaggregate dimension name.
    /// </summary>
    [JsonObject]
    internal sealed class PreaggregateDimensionFilterValues
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PreaggregateDimensionFilterValues"/> class.
        /// </summary>
        /// <param name="filterDimensionName">Name of the filter dimension.</param>
        /// <param name="filterValues">The filter values.</param>
        [JsonConstructor]
        public PreaggregateDimensionFilterValues(string filterDimensionName, IReadOnlyList<string> filterValues)
        {
            if (string.IsNullOrEmpty(filterDimensionName))
            {
                throw new ArgumentNullException(nameof(filterDimensionName));
            }

            if (filterValues == null)
            {
                throw new ArgumentNullException(nameof(filterValues));
            }

            foreach (var value in filterValues)
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentException($"{nameof(filterValues)} cannot have empty of null values");
                }
            }

            this.FilterDimensionName = filterDimensionName;
            this.FilterValues = new HashSet<string>(filterValues, StringComparer.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Gets the name of the filter dimension.
        /// </summary>
        public string FilterDimensionName { get; }

        /// <summary>
        /// Gets the filter values.
        /// </summary>
        public ISet<string> FilterValues { get; }
    }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Cloud.Metrics.Client;
using Microsoft.Cloud.Metrics.Client.Metrics;
using Microsoft.Cloud.Metrics.Client.Query;
using Microsoft.Online.Metrics.Serialization.Configuration;

namespace MetricsMonitor
{
    public class MdmClient
    {
        public static readonly string MessageCount = "MessageCount";
        public static readonly string PodConnectionCount = "PodConnectionCount";

        private readonly X509Certificate2 _certificate;
        private readonly MdmEnvironment _mdmEnvironment = MdmEnvironment.Production;
        private readonly string _namespace = "Shoebox";
        private readonly string _namespace2 = "ShoeboxInternal";
        private readonly string _accountPrefix = "MicrosoftSignalRServiceShoebox";
        private string _account;

        public MdmClient(string certPath, string passwd, string region)
        {
            _certificate = new X509Certificate2(certPath, passwd);
            _account = $"{_accountPrefix}{region}";
        }

        public async Task<IDictionary<string, double>> GetDimensionCountMetricsAsync(string metricName, TimeSpan backTime, string dimension)
        {
            if (MessageCount.Equals(metricName))
            {
                return await GetDimensionMessageCountMetricsAsync(backTime, dimension);
            }
            else
            {
                if (PodConnectionCount.Equals(metricName))
                {
                    return await GetDimensionPodConnectionCountMetricsAsync(backTime, dimension);
                }
            }
            return null;
        }

        public async Task<IDictionary<string, double>> GetDimensionPodConnectionCountMetricsAsync(TimeSpan backTime, string dimension)
        {
            return await GetDimensionMetricsCountCoreAsync(PodConnectionCount, backTime, dimension);
        }

        public async Task<IDictionary<string, double>> GetDimensionMessageCountMetricsAsync(TimeSpan backTime, string dimension)
        {
            return await GetDimensionMetricsCountCoreAsync(MessageCount, backTime, dimension);
        }

        public static bool isShoeboxNamespace(string metricName)
        {
            switch (metricName)
            {
                case "MessageCount":
                case "ConnectionCount":
                case "InboundTraffic":
                case "OutboundTraffic":
                    return true;
                case "PodConnectionCount":
                case "RedisPubCount":
                case "TotalDelta":
                case "ConnectionCountRaw":
                    return false;
            }
            return false;
        }

        public MetricIdentifier GenMetricId(string metricName)
        {
            if (isShoeboxNamespace(metricName))
            {
                return new MetricIdentifier(_account, _namespace, metricName);
            }
            else
            {
                return new MetricIdentifier(_account, _namespace2, metricName);
            }
        }

        public async Task<IDictionary<string, double>> GetDimensionMetricsCountCoreAsync(string metricsName, TimeSpan backTime, string dimension)
        {
            IDictionary<string, double> result = new Dictionary<string, double>();
            var connectionInfo = new ConnectionInfo(_certificate, _mdmEnvironment);
            var metricReader = new MetricReader(connectionInfo);
            var metricId = GenMetricId(metricsName);
            var dimensionFilters = new List<DimensionFilter>
            {
                DimensionFilter.CreateIncludeFilter(dimension)
            };

            try
            {
                var now = DateTime.UtcNow;
                var results = await metricReader.GetTimeSeriesAsync(
                    metricId,
                    dimensionFilters,
                    now - backTime,
                    now,
                    new[] { SamplingType.Count },
                    new SelectionClauseV3(new PropertyDefinition(PropertyAggregationType.Sum, SamplingType.Count), 100_000, OrderBy.Descending),
                    outputDimensionNames: new List<string> { dimension }
                );
                return results.Results.ToDictionary(r => r.DimensionList.First().Value, r => r.EvaluatedResult);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Failed to get metrics data from Geneva", ex);
                return null;
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HttpClientHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Net.Security;
    using System.Reflection;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using Logging;
    using Newtonsoft.Json;
    using Online.Metrics.Serialization;

    /// <summary>
    /// The http client helper class.
    /// </summary>
    public static class HttpClientHelper
    {
        /// <summary>
        /// The HTTP status code for too many requests.
        /// </summary>
        /// <remarks>
        /// Refer: http://tools.ietf.org/html/rfc6585.
        /// </remarks>
        public const int HttpStatusCodeThrottled = 429;

        /// <summary>
        /// The white listed server subject names.
        /// </summary>
        public static readonly HashSet<string> WhiteListedServerSubjectNames = new HashSet<string>(new[] { "CN=*.dc.ad.msft.net", "CN=*.test.dc.ad.msft.net", "CN=*.ff.dc.ad.msft.net", "CN=*.test.ff.dc.ad.msft.net", "CN=*.cn.dc.ad.msft.net, O=Shanghai Blue Cloud Technology Co. Ltd, L=Shanghai, C=CN" });

        /// <summary>
        /// The default maximum web requests per minute
        /// </summary>
        private const int DefaultMaxWebRequestsPerMinute = 1000;

        /// <summary>
        /// The throttled identity http header name.
        /// </summary>
        private const string ThrottledIdentityKey = "Throttled-Identity";

        /// <summary>
        /// The retry after http header name.
        /// </summary>
        private const string RetryAfterKey = "Retry-After";

        /// <summary>
        /// The version of the current metrics client assembly
        /// </summary>
        private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();

        /// <summary>
        /// The log identifier for this class
        /// </summary>
        private static readonly object LogId = Logger.CreateCustomLogId("HttpClientHelper");

        /// <summary>
        /// The identities throttled by server.
        /// Keys are monitoring accounts.
        /// Values are throttled identities to DateTime to proceed sending requests.
        /// </summary>
        private static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, DateTime>> ServerThrottledIdentities;

        /// <summary>
        /// The current minute of the hour
        /// </summary>
        private static volatile int currentMinute = DateTime.UtcNow.Minute;

        /// <summary>
        /// The requests sent in current minute
        /// </summary>
        private static int requestsSentInCurrentMinute;

        /// <summary>
        /// Initializes static members of the <see cref="HttpClientHelper"/> class.
        /// </summary>
        static HttpClientHelper()
        {
            EnableHttpPipelining = false;
            ServerThrottledIdentities = new ConcurrentDictionary<string, ConcurrentDictionary<string, DateTime>>();
        }

        /// <summary>
        /// Gets or sets the max web requests per minute.
        /// </summary>
        public static ushort MaxWebRequestsPerMinute { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether not to set certificate validation callback so that consumers control it.
        /// </summary>
        public static bool DoNotSetCertificateValidationCallback { get; set; }

        /// <summary>
        /// Gets or sets a value indicating if http pipelining should be enabled or not for http(s) requests.
        ///
        /// This is disabled by default.
        /// </summary>
        public static bool EnableHttpPipelining { get; set; }

        /// <summary>
        /// Gets the authentication header
        /// </summary>
        /// <returns>The authentication header</returns>
        public static AuthenticationHeaderValue GetAuthenticationHeader()
        {
            return new AuthenticationHeaderValue(UserAccessTokenRefresher.BearerTokenAuthScheme, UserAccessTokenRefresher.Instance.UserAccessToken);
        }

        /// <summary>
        /// Creates the HTTP client with user or cert authentication.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        /// <param name="authHeaderValue">The authentication header value.</param>
        /// <returns>An instance of <see cref="HttpClient"/></returns>
        public static HttpClient CreateHttpClientWithAuthInfo(ConnectionInfo connectionInfo, string authHeaderValue = null)
        {
            HttpClient httpClient;
            var webRequestHandler = new HttpClientHandler { AllowAutoRedirect = false, };
            if (!DoNotSetCertificateValidationCallback)
            {
                webRequestHandler.ServerCertificateCustomValidationCallback = CertificateValidationCallback;
            }

            if (connectionInfo.UseAadUserAuthentication)
            {
                httpClient = new HttpClient(webRequestHandler, disposeHandler: true);

                AuthenticationHeaderValue authHeader;
                if (authHeaderValue == null)
                {
                    authHeader = GetAuthenticationHeader();
                }
                else
                {
                    authHeader = new AuthenticationHeaderValue(UserAccessTokenRefresher.BearerTokenAuthScheme, authHeaderValue);
                }

                httpClient.DefaultRequestHeaders.Authorization = authHeader;
            }
            else
            {
                webRequestHandler.ClientCertificates.Add(connectionInfo.Certificate);
                httpClient = new HttpClient(webRequestHandler, disposeHandler: true);
            }

            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(SerializationConstants.OctetStreamContentType));
            httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MultiDimensionalMetricsClient");
            httpClient.DefaultRequestHeaders.Add("MultiDimensionalMetricsClientVersion", AssemblyVersion);

            if (connectionInfo.AdditionalDefaultRequestHeaders != null)
            {
                foreach (var kvp in connectionInfo.AdditionalDefaultRequestHeaders)
                {
                    httpClient.DefaultRequestHeaders.Add(kvp.Key, kvp.Value);
                }
            }

            httpClient.Timeout = connectionInfo.Timeout;

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                "CreateClient",
                "Created new HttpClient. CertThumbprint:{0}, TimeoutMs:{1}",
                connectionInfo.CertificateThumbprint,
                connectionInfo.Timeout.TotalMilliseconds);

            return httpClient;
        }

        /// <summary>
        /// Creates the HTTP client
        /// </summary>
        /// <param name="timeout">The timeout to apply to the requests.</param>
        /// <returns>
        /// An instance of <see cref="HttpClient" />
        /// </returns>
        public static HttpClient CreateHttpClient(TimeSpan timeout)
        {
            var webRequestHandler = new HttpClientHandler { AllowAutoRedirect = false };
            if (!DoNotSetCertificateValidationCallback)
            {
                webRequestHandler.ServerCertificateCustomValidationCallback = CertificateValidationCallback;
            }

            HttpClient httpClient = new HttpClient(webRequestHandler, disposeHandler: true);
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(SerializationConstants.OctetStreamContentType));

            httpClient.Timeout = timeout;

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                "CreateClient",
                "Created new HttpClient. TimeoutMs:{0}",
                timeout.TotalMilliseconds);

            return httpClient;
        }

        /// <summary>
        /// Callback when a server side certificate is validated.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="certificate">The certificate.</param>
        /// <param name="chain">The chain.</param>
        /// <param name="sslPolicyErrors">The SSL policy errors.</param>
        /// <returns>True if no errors found while validation.</returns>
        public static bool CertificateValidationCallback(
            object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors)
        {
            // MDM is using 3rd party domains, metrics.nsatc.net, to reduce the dependency on MSFT infrasture, so we cannot obtain a certificate matching the domains.
            // We will consider moving to MSFT domains but for now we customize the validation on cert subject name for requests toward MDM stamp hostnames or IPs.
            return sslPolicyErrors == SslPolicyErrors.None
                   || (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch && WhiteListedServerSubjectNames.Contains(certificate.Subject));
        }

        /// <summary>
        /// Adds the standard headers to message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="traceId">The trace identifier that will be placed in a header.</param>
        /// <param name="sourceIdentity">The source identity that will be placed in a header.</param>
        /// <param name="hostname">The host name of an endpoint.</param>
        public static void AddStandardHeadersToMessage(HttpRequestMessage message, Guid traceId, string sourceIdentity, string hostname)
        {
            message.Headers.Add(SerializationConstants.TraceIdHeader, traceId.ToString("B"));
            message.Headers.Add("SourceIdentity", sourceIdentity);
            message.Headers.Host = hostname;
        }

        /// <summary>
        /// Gets the response as string.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="method">The http method.</param>
        /// <param name="client">The HTTP client.</param>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="operation">The operation.</param>
        /// <param name="httpContent">Content of the HTTP request.</param>
        /// <param name="clientId">Optional parameter identifying client.</param>
        /// <param name="serializedContent">Serialized content of the HTTP request, if special serialization is needed.</param>
        /// <param name="traceId">The trace identifier.</param>
        /// <param name="serializationVersion">The serialization version.</param>
        /// <param name="numAttempts">The number of attempts.</param>
        /// <returns>
        /// The HTTP response message as a string.
        /// </returns>
        /// <remarks>
        /// We attempt up to 3 times with delay of 5 seconds and 10 seconds in between respectively, if the request cannot be sent or the response status code is 503.
        /// However, we don't want to retry in the OBO case.
        /// </remarks>
        public static async Task<string> GetResponseAsStringAsync(
            Uri url,
            HttpMethod method,
            HttpClient client,
            string monitoringAccount,
            string operation,
            object httpContent = null,
            string clientId = "",
            string serializedContent = null,
            Guid? traceId = null,
            byte serializationVersion = MetricQueryResponseDeserializer.CurrentVersion,
            int numAttempts = 3)
        {
            Tuple<string, HttpResponseMessage> response = null;
            try
            {
                response = await GetResponse(
                        url,
                        method,
                        client,
                        monitoringAccount,
                        operation,
                        httpContent,
                        clientId,
                        serializedContent,
                        traceId,
                        serializationVersion,
                        numAttempts)
                    .ConfigureAwait(false);

                return response.Item1;
            }
            finally
            {
                response?.Item2?.Dispose();
            }
        }

        /// <summary>
        /// Gets the HTTP response message as a string.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <param name="method">The http method.</param>
        /// <param name="client">The HTTP client.</param>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="operation">The operation.</param>
        /// <param name="httpContent">Content of the HTTP request.</param>
        /// <param name="clientId">Optional parameter identifying client.</param>
        /// <param name="serializedContent">Serialized content of the HTTP request, if special serialization is needed.</param>
        /// <param name="traceId">The trace identifier.</param>
        /// <param name="serializationVersion">The serialization version.</param>
        /// <param name="numAttempts">The number of attempts.</param>
        /// <returns>
        /// The HTTP response message as a string.
        /// </returns>
        /// <remarks>
        /// We attempt up to 3 times with delay of 5 seconds and 10 seconds in between respectively, if the request cannot be sent or the response status code is 503.
        /// However, we don't want to retry in the OBO case.
        /// </remarks>
        public static async Task<Tuple<string, HttpResponseMessage>> GetResponse(
            Uri url,
            HttpMethod method,
            HttpClient client,
            string monitoringAccount,
            string operation,
            object httpContent = null,
            string clientId = "",
            string serializedContent = null,
            Guid? traceId = null,
            byte serializationVersion = MetricQueryResponseDeserializer.CurrentVersion,
            int numAttempts = 3)
        {
            const int baseWaitTimeInSeconds = 5;
            Exception lastException = null;

            if (IsThrottledByServer(monitoringAccount, operation, method))
            {
                throw new MetricsClientException(
                    $"The request is throttled by the server. Url:{url}, Method:{method}, Operation:{operation}.",
                    null,
                    traceId ?? Guid.Empty,
                    (HttpStatusCode)HttpStatusCodeThrottled);
            }

            var stopWatch = Stopwatch.StartNew();
            for (int i = 1; i <= numAttempts; i++)
            {
                try
                {
                    return await GetResponseWithTokenRefresh(url, method, client, httpContent, clientId, serializedContent, traceId, serializationVersion, monitoringAccount).ConfigureAwait(false);
                }
                catch (MetricsClientException e)
                {
                    lastException = e;

                    if (stopWatch.Elapsed >= client.Timeout ||
                        (e.ResponseStatusCode != null && e.ResponseStatusCode != HttpStatusCode.ServiceUnavailable) ||
                        i == numAttempts)
                    {
                        throw;
                    }

                    var delay = TimeSpan.FromSeconds(baseWaitTimeInSeconds * i);

                    Logger.Log(LoggerLevel.Info, LogId, "GetResponse", "Delay {0} and then retry.", delay);
                    await Task.Delay(delay).ConfigureAwait(false);
                }
            }

            throw new MetricsClientException($"Exhausted {numAttempts} attempts.", lastException);
        }

        private static async Task<Tuple<string, HttpResponseMessage>> GetResponseWithTokenRefresh(
            Uri url,
            HttpMethod method,
            HttpClient client,
            object httpContent,
            string clientId,
            string serializedContent,
            Guid? traceId,
            byte serializationVersion,
            string monitoringAccount)
        {
            try
            {
                return await GetResponseNoRetry(url, method, client, httpContent, clientId, serializedContent, traceId, serializationVersion, monitoringAccount).ConfigureAwait(false);
            }
            catch (MetricsClientException e)
            {
                if (e.ResponseStatusCode == HttpStatusCode.Redirect)
                {
                    await UserAccessTokenRefresher.Instance.RefreshAccessToken().ConfigureAwait(false);
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(UserAccessTokenRefresher.BearerTokenAuthScheme, UserAccessTokenRefresher.Instance.UserAccessToken);
                    return await GetResponseNoRetry(url, method, client, httpContent, clientId, serializedContent, traceId, serializationVersion, monitoringAccount).ConfigureAwait(false);
                }

                throw;
            }
        }

        private static async Task<Tuple<string, HttpResponseMessage>> GetResponseNoRetry(
            Uri url,
            HttpMethod method,
            HttpClient client,
            object httpContent,
            string clientId,
            string serializedContent,
            Guid? traceId,
            byte serializationVersion,
            string monitoringAccount)
        {
            const string LogTag = "GetResponse";

            traceId = traceId ?? Guid.NewGuid();

            HandleGeneralClientThrottling(url, method, traceId.Value);
            var ipUrlBuilder = new UriBuilder(url)
            {
                Host = (await ConnectionInfo.GetCachedIpAddress(url).ConfigureAwait(false)).Host
            };

            var request = new HttpRequestMessage(method, ipUrlBuilder.Uri);
            var sourceId = Environment.MachineName;
            AddStandardHeadersToMessage(request, traceId.Value, sourceId, url.Host);
            if (!string.IsNullOrWhiteSpace(clientId))
            {
                request.Headers.Add(SerializationConstants.ClientIdHeader, clientId);
            }

            if (serializationVersion == 1)
            {
                // We won't use this header for new versions of serialization.
                request.Headers.Add(SerializationConstants.ScalingFactorDisabledHeader, "true");
            }

            if (httpContent != null && serializedContent == null)
            {
                serializedContent = JsonConvert.SerializeObject(httpContent);
            }

            if (serializedContent != null)
            {
                request.Content = new StringContent(serializedContent, Encoding.UTF8, "application/json");
            }

            Logger.Log(
                LoggerLevel.Info,
                LogId,
                LogTag,
                "Making HTTP request. TraceId:{0}, Url:{1}, Method:{2}, SourceId:{3}, ContentLength:{4}, DnsTimeoutMs:{5}, TimeoutMs:{6}, SdkVersion:{7}",
                traceId,
                url,
                method,
                sourceId,
                serializedContent?.Length ?? 0,
                ServicePointManager.DnsRefreshTimeout,
                client.Timeout.TotalMilliseconds,
                AssemblyVersion);

            string responseString = null;
            var requestLatency = Stopwatch.StartNew();
            var stage = "SendRequest";
            var handlingServer = "Unknown";
            HttpResponseMessage response = null;
            try
            {
                response = await client.SendAsync(request).ConfigureAwait(false);
                Logger.Log(
                    LoggerLevel.Info,
                    LogId,
                    LogTag,
                    "Sent HTTP request, reading response. TraceId:{0}, Url:{1}, SendLatencyMs:{2}",
                    traceId,
                    url,
                    requestLatency.ElapsedMilliseconds);

                stage = "ReadResponse";

                IEnumerable<string> handlingServerValues;
                response.Headers.TryGetValues("__HandlingServerId__", out handlingServerValues);
                if (handlingServerValues != null)
                {
                    handlingServer = handlingServerValues.First();
                }

                requestLatency.Restart();

                if (response.Content.Headers.ContentType?.MediaType != null
                    && response.Content.Headers.ContentType.MediaType.Equals(SerializationConstants.OctetStreamContentType, StringComparison.OrdinalIgnoreCase))
                {
                    responseString = SerializationConstants.OctetStreamContentType;
                }
                else
                {
                    responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                }

                Logger.Log(
                    LoggerLevel.Info,
                    LogId,
                    LogTag,
                    "Received HTTP response. TraceId:{0}, Url:{1}, HandlingServer:{2}, ReadLatencyMs:{3}, ResponseStatus:{4}, ResponseLength:{5}",
                    traceId,
                    url,
                    handlingServer,
                    requestLatency.ElapsedMilliseconds,
                    response.StatusCode,
                    responseString?.Length ?? 0);

                stage = "ValidateStatus";
                response.EnsureSuccessStatusCode();

                return Tuple.Create(responseString, response);
            }
            catch (Exception e)
            {
                var message = HandleServerSideThrottling(response, url, method, monitoringAccount) ?
                    $"HTTP request throttled by server. Url:{url}, Method:{method}, Response:{responseString ?? "<none>"}" :
                    $"Failed to get a response from the server. TraceId:{traceId.Value.ToString("B")}, Url:{request.RequestUri}, HandlingServer:{handlingServer} Stage:{stage}, "
                    +
                    $"LatencyMs:{requestLatency.ElapsedMilliseconds}, ResponseStatus:{response?.StatusCode.ToString() ?? "<none>"}, Response:{responseString}.";

                Logger.Log(LoggerLevel.Error, LogId, LogTag, message);

                throw new MetricsClientException(message, e, traceId.Value, response?.StatusCode);
            }
            finally
            {
                requestLatency.Stop();
            }
        }

        private static bool HandleServerSideThrottling(
            HttpResponseMessage response,
            Uri url,
            HttpMethod method,
            string monitoringAccount)
        {
            if (response != null &&
                (int)response.StatusCode == HttpStatusCodeThrottled &&
                response.Headers != null)
            {
                if (!response.Headers.Contains(ThrottledIdentityKey) || !response.Headers.Contains(RetryAfterKey))
                {
                    return false;
                }

                string throttledIdentity = response.Headers.GetValues(ThrottledIdentityKey).FirstOrDefault();
                if (string.IsNullOrEmpty(throttledIdentity))
                {
                    return false;
                }

                string retryAfter = response.Headers.GetValues(RetryAfterKey).FirstOrDefault();
                if (string.IsNullOrEmpty(retryAfter))
                {
                    return false;
                }

                int retyAfterSeconds;

                if (int.TryParse(retryAfter, out retyAfterSeconds))
                {
                    DateTime now = DateTime.UtcNow;

                    ServerThrottledIdentities.AddOrUpdate(
                        monitoringAccount,
                        key =>
                        {
                            var value = new ConcurrentDictionary<string, DateTime>();

                            value.TryAdd(throttledIdentity, now.AddSeconds(retyAfterSeconds));

                            return value;
                        },
                        (key, value) =>
                        {
                            value.AddOrUpdate(
                                throttledIdentity,
                                keyInner => now.AddSeconds(retyAfterSeconds),
                                (keyInner, valueInner) => now.AddSeconds(retyAfterSeconds));

                            return value;
                        });

                    return true;
                }

                Logger.Log(
                    LoggerLevel.Debug,
                    LogId,
                    "HandleServerSideThrottling",
                    "HTTP request throttled by server, but we could not parse retry-after header Url:{0}, Method:{1}, Retry-After {2}",
                    url,
                    method);
            }

            return false;
        }

        private static bool IsThrottledByServer(string monitoringAccount, string operation, HttpMethod httpMethod)
        {
            if (string.IsNullOrEmpty(monitoringAccount))
            {
                return false;
            }

            ConcurrentDictionary<string, DateTime> throttingIdentityToTimeToProceed;

            if (!ServerThrottledIdentities.TryGetValue(monitoringAccount, out throttingIdentityToTimeToProceed))
            {
                return false;
            }

            DateTime timeToProceedByOperation;
            throttingIdentityToTimeToProceed.TryGetValue(operation, out timeToProceedByOperation);

            DateTime timeToProceedByHttpMethod;
            throttingIdentityToTimeToProceed.TryGetValue(httpMethod.ToString(), out timeToProceedByHttpMethod);

            DateTime timeToProceed = new DateTime(Math.Max(timeToProceedByOperation.Ticks, timeToProceedByHttpMethod.Ticks));

            if (timeToProceed <= DateTime.UtcNow)
            {
                ConcurrentDictionary<string, DateTime> dummy;
                ServerThrottledIdentities.TryRemove(monitoringAccount, out dummy);
                return false;
            }

            return true;
        }

        private static void HandleGeneralClientThrottling(
            Uri url,
            HttpMethod method,
            Guid traceId)
        {
            var minute = DateTime.UtcNow.Minute;

            if (minute == currentMinute)
            {
                Interlocked.Increment(ref requestsSentInCurrentMinute);
            }
            else
            {
                currentMinute = minute;
                Interlocked.Exchange(ref requestsSentInCurrentMinute, 0);
            }

            var effectiveMaxWebRequestsPerMinute = Math.Max(DefaultMaxWebRequestsPerMinute, (int)MaxWebRequestsPerMinute);

            if (requestsSentInCurrentMinute > effectiveMaxWebRequestsPerMinute)
            {
                Logger.Log(
                    LoggerLevel.Debug,
                    LogId,
                    "HandleGeneralClientThrottling",
                    "HTTP request throttled. Url:{0}, Method:{2}, CurrentRequestsInMinute:{3}, AllowedRequestsInMinute:{4}",
                    url,
                    method,
                    requestsSentInCurrentMinute,
                    effectiveMaxWebRequestsPerMinute);

                throw new MetricsClientException(
                    $"Client size throttling: no more than [{effectiveMaxWebRequestsPerMinute}] requests can be issued in a minute.",
                    null,
                    traceId,
                    (HttpStatusCode)HttpStatusCodeThrottled);
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TraceEventHeader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Type that wraps access to the header fields of an ETW event record.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// Type that wraps access to the header fields of an ETW event record.
    /// </summary>
    internal unsafe struct TraceEventHeader
    {
        /// <summary>
        /// Pointer to the native structure being wrapped.
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2151:FieldsWithCriticalTypesShouldBeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        private readonly NativeMethods.EventHeader* eventHeader;

        /// <summary>
        /// Initializes a new instance of the <see cref="TraceEventHeader"/> struct.
        /// </summary>
        /// <param name="eventHeader">
        /// Pointer to the native structure being wrapped by the instance.
        /// </param>
        public TraceEventHeader(NativeMethods.EventHeader* eventHeader)
        {
            if (eventHeader == null)
            {
                throw new ArgumentNullException("eventHeader");
            }

            this.eventHeader = eventHeader;
        }

        /// <summary>
        /// Gets the size of the event record, in bytes.
        /// </summary>
        public ushort Size
        {
            get
            {
                return this.eventHeader->Size;
            }
        }

        /// <summary>
        /// Gets the header eventType (reserved).
        /// </summary>
        public ushort HeaderType
        {
            get
            {
                return this.eventHeader->HeaderType;
            }
        }

        /// <summary>
        /// Gets the flags that provide information about the event such as the eventType
        /// of model it was logged to and if the event contains extended data.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Opcode", Justification = "As commonly used in ETW")]
        public ushort Flags
        {
            get
            {
                return this.eventHeader->Flags;
            }
        }

        /// <summary>
        /// Gets the eventType of source to use for parsing the event data.
        /// </summary>
        public ushort EventProperty
        {
            get
            {
                return this.eventHeader->EventProperty;
            }
        }

        /// <summary>
        /// Gets the thread that generated the event.
        /// </summary>
        public int ThreadId
        {
            get
            {
                return this.eventHeader->ThreadId;
            }
        }

        /// <summary>
        /// Gets the process that generated the event.
        /// </summary>
        public int ProcessId
        {
            get
            {
                return this.eventHeader->ProcessId;
            }
        }

        /// <summary>
        /// Gets the the time that the event occurred. The resolution depends on the value
        /// of the <c>Wnode.ClientContext</c> member of <c>EVENT_TRACE_PROPERTIES</c> at the time the
        /// controller created model.
        /// </summary>
        public long Timestamp
        {
            get
            {
                return this.eventHeader->TimeStamp;
            }
        }

        /// <summary>
        /// Gets the GUID that uniquely identifies the provider that logged the event.
        /// </summary>
        public Guid ProviderId
        {
            get
            {
                return this.eventHeader->ProviderId;
            }
        }

        /// <summary>
        /// Gets the Id of the event.
        /// </summary>
        public ushort Id
        {
            get
            {
                return this.eventHeader->Id;
            }
        }

        /// <summary>
        /// Gets the version of the event.
        /// </summary>
        public byte Version
        {
            get
            {
                return this.eventHeader->Version;
            }
        }

        /// <summary>
        /// Gets the channel of the event.
        /// </summary>
        public byte Channel
        {
            get
            {
                return this.eventHeader->Channel;
            }
        }

        /// <summary>
        /// Gets the level of the event.
        /// </summary>
        public byte Level
        {
            get
            {
                return this.eventHeader->Level;
            }
        }

        /// <summary>
        /// Gets the <c>opcode</c> of the event.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Opcode", Justification = "As commonly used in ETW")]
        public byte Opcode
        {
            get
            {
                return this.eventHeader->Opcode;
            }
        }

        /// <summary>
        /// Gets the task of the event.
        /// </summary>
        public ushort Task
        {
            get
            {
                return this.eventHeader->Task;
            }
        }

        /// <summary>
        /// Gets the keyword of the event.
        /// </summary>
        public ulong Keyword
        {
            get
            {
                return this.eventHeader->Keyword;
            }
        }

        /// <summary>
        /// Gets the elapsed execution time for kernel-mode instructions, in CPU time units.
        /// </summary>
        public int KernelTime
        {
            get
            {
                return this.eventHeader->KernelTime;
            }
        }

        /// <summary>
        /// Gets the elapsed execution time for user-mode instructions, in CPU time units.
        /// </summary>
        public int UserTime
        {
            get
            {
                return this.eventHeader->UserTime;
            }
        }

        /// <summary>
        /// Gets an identifier that relates two events. For details, see EventWriteTransfer.
        /// </summary>
        public Guid ActivityId
        {
            get
            {
                return this.eventHeader->ActivityId;
            }
        }
    }
}
<file_sep>﻿//-----------------------------------------------------------------------
// <copyright file="ConfigurationValidationException.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;

    /// <summary>
    /// The type of validation being performed at the time of the failure.
    /// </summary>
    public enum ValidationType
    {
        ServerSide,
        DuplicateMetricSource,
        DuplicateDimension,
        DuplicatePreaggregate,
        DuplicateSamplingType,
        BuiltInTypeRemoved,
    }

    /// <summary>
    /// Exception thrown when account object cannot be found.
    /// </summary>
    public sealed class ConfigurationValidationException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigurationValidationException"/> class.
        /// </summary>
        /// <param name="message">Message describing exception situation.</param>
        /// <param name="validationType">The type of the validation that failed.</param>
        public ConfigurationValidationException(string message, ValidationType validationType)
            : base(message)
        {
            this.ValidationType = validationType;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigurationValidationException"/> class.
        /// </summary>
        /// <param name="message">Message describing exception situation.</param>
        /// <param name="validationType">The type of the validation that failed.</param>
        /// <param name="innerException">Inner exception which caused exception situation.</param>
        public ConfigurationValidationException(string message, ValidationType validationType, Exception innerException)
            : base(message, innerException)
        {
            this.ValidationType = validationType;
        }

        /// <summary>
        /// The type of the validation which failed.
        /// </summary>
        public ValidationType ValidationType { get; }
    }
}
<file_sep># Stamp Locator

Date: 2/11/2019

## Status

## Context

Third party monitoring accounts use a different tenancy model, i.e., the same account exists on all azmon regional stamps.
Therefore, to query metrics, partner team(s) need to use our client API to determine the target regional stamp first for a given region.
There is a hard coded region to MDM stamp mapping in StampLocator of the client library, 
so partner team(s) need to take a new client library Nuget package when a new region support is added. 

## Decision

We will create a new *public* API in FE to return this mapping. To improve the reliability: 
1) We will have a new DNS name *stamplocator.metrics.nsatc.net* which includes 3 stamps in 3 stamp groups in 3 different continents. 
The 3 stamps are azglobal, azmonsuk, and azmonejp in priority/tier order. 
2) StampLocator.CreateInstanceAsync asks for a folder on the local disk to cache this mapping in case no MDM stamp is reachable. 
The folder can be provisioned as an Azure local resource so that it can survive machine reimaging. 
If no MDM stamp is reachable and no local cache is available, StampLocator.CreateInstanceAsync will throw; 
otherwise it will succeed and auto-refresh will happen in the background hourly.
We expose the following activities for partner team(s) to add monitoring and logging.


```csharp
    /// <summary>
    /// Creates an instance of <see cref="IStampLocator"/> asynchronously.
    /// </summary>
    /// <param name="folderToCacheRegionStampMap">The folder to cache region stamp map.</param>
    /// <param name="activityReporter">The activity reporter. The string argument contains the error detail when the activity results in an error; otherwise it is null.</param>
    /// <returns>An instance of <see cref="StampLocator"/>.</returns>
    public static Task<IStampLocator> CreateInstanceAsync(string folderToCacheRegionStampMap, Action<StampLocatorActivity, string> activityReporter)

    /// <summary>
    /// The stamp locator activities.
    /// </summary>
    public enum StampLocatorActivity
    {
        /// <summary>
        /// Refreshing the region stamp map from the MDM backend API.
        /// </summary>
        StartToRefrehRegionStampMap,
        FinishedRefreshingRegionStampMap,
        FailedToRefrehRegionStampMap,

        /// <summary>
        /// Loading the region stamp map from the local file regionStampMap.json.
        /// </summary>
        StartToLoadRegionStampMapFromLocalFile,
        FinishedLoadingRegionStampMapFromLocalFile,
        FailedToLoadRegionStampMapFromLocalFile,

        /// <summary>
        /// Writing the region stamp map to the local file regionStampMap.json.
        /// </summary>
        StartToWriteRegionStampMapToLocalFile,
        FinishedWritingRegionStampMapToLocalFile,
        FailedToWriteRegionStampMapToLocalFile,
    }

```<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Logger.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Logging
{
    using System;
    using System.Linq;

    /// <summary>
    /// Static type that provides a "globally" available logging mechanism.
    /// </summary>
    /// <remarks>
    /// Implemented as a static so this does not need to be passed to every and each
    /// single type performing any logging, the actual engine doing the logging is
    /// defined via interface that should be set for this static as early as possible in
    /// the lifetime of the application.
    /// </remarks>
    public static class Logger
    {
        private static ILogEngine[] logEngines = { new ConsoleLogEngine(), EventSourceLogEngine.Logger };
        private static LoggerLevel maxLogLevel = LoggerLevel.Info;

        /// <summary>
        /// Gets or sets a value indicating whether to disable logging.
        /// </summary>
        public static bool DisableLogging { get; set; }

        /// <summary>
        /// Sets the log engines to be used for logging. This method is not
        /// thread safe, the typical scenario is for it to be called once at the
        /// start of the program and not anymore. Warning: when the log engine is changed
        /// any customer log id created via the CreateCustomLogId method should be considered
        /// invalidated and as such objects need to be re-created.
        /// </summary>
        /// <param name="logEngines">
        /// Log engines to be used to produce the logs.
        /// </param>
        public static void SetLogEngine(params ILogEngine[] logEngines)
        {
            if (logEngines == null)
            {
                throw new ArgumentNullException("logEngines");
            }

            Logger.logEngines = logEngines;
        }

        /// <summary>
        /// Sets the maximum log level, anything above this level should not be
        /// logged by the engine.
        /// </summary>
        /// <param name="level">
        /// Lower or equal to it should be logged by the engine, anything above it
        /// should not be logged.
        /// </param>
        public static void SetMaxLogLevel(LoggerLevel level)
        {
            maxLogLevel = level;
        }

        /// <summary>
        /// Checks if a log statement with the given parameters will be actually logged or
        /// not. Useful to avoid expensive operations for log statements that are going to
        /// be dropped by the log engine.
        /// </summary>
        /// <param name="level">
        /// Level of the log statement.
        /// </param>
        /// <param name="logId">
        /// Log identification for classifying log statements.
        /// </param>
        /// <param name="tag">
        /// Extra string that allows another level of classification under the log id.
        /// </param>
        /// <returns>
        /// True if the statement is going to be logged, false otherwise.
        /// </returns>
        internal static bool IsLogged(LoggerLevel level, object logId, string tag)
        {
            return (level <= maxLogLevel) && logEngines.Any(e => e.IsLogged(level, logId, tag));
        }

        /// <summary>
        /// Gets the maximum log level, anything above this level should not be
        /// logged by the engine.
        /// </summary>
        /// <returns>
        /// The maximum log level current in use by the logger.
        /// </returns>
        internal static LoggerLevel GetMaxLogLevel()
        {
            return maxLogLevel;
        }

        /// <summary>
        /// Creates a custom object used by the logger to help identifying a boundary of
        /// logging (e.g.: component, object, service, etc). Warning: these objects are
        /// specific for each implementation of a log engine. If the log engine is changed,
        /// via SetLogEngine, all previously created such objects need to be re-created for
        /// correct usage with the current log engine.
        /// </summary>
        /// <param name="logIdName">
        /// Friendly name to be associated with the log id.
        /// </param>
        /// <returns>
        /// The object to be used a log id - these objects are specific for each implementation
        /// of a log engine. If the log engine is changed, via SetLogEngine, all previously
        /// created such objects need to be re-created for correct usage with the current
        /// log engine.
        /// </returns>
        internal static object CreateCustomLogId(string logIdName)
        {
            if (string.IsNullOrEmpty(logIdName))
            {
                throw new ArgumentNullException("logIdName");
            }

            return logIdName;
        }

        /// <summary>
        /// Logs the given data according to the log engine previously set.
        /// </summary>
        /// <param name="level">
        /// Level of the log statement.
        /// </param>
        /// <param name="logId">
        /// Log identification for classifying log statements and also any object
        /// that a specific log engine may need to perform its logging. All such
        /// objects created before a call to SetLogEngine a different log engine
        /// should be re-created with the log engine currently in use.
        /// </param>
        /// <param name="tag">
        /// Extra string that allows another level of classification under the log id.
        /// </param>
        /// <param name="format">
        /// Message to be logged, it can be a format message.
        /// </param>
        /// <param name="objectParams">
        /// Optional, any parameter to be used to build the formatted message string.
        /// </param>
        internal static void Log(
            LoggerLevel level, object logId, string tag, string format, params object[] objectParams)
        {
            // Do only the cheap log level checking here, the other checking is left to the
            // log engine.
            if (level <= maxLogLevel && !DisableLogging)
            {
                foreach (var engine in logEngines)
                {
                    engine.Log(level, logId, tag, format, objectParams);
                }
            }
        }
    }
}
<file_sep>﻿// <copyright file="IStampLocator.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

namespace Microsoft.Cloud.Metrics.Client.ThirdParty
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Interface for locating the stamp endpoint for Azure external customers' 3rd party accounts.
    /// </summary>
    public interface IStampLocator
    {
        /// <summary>
        /// Gets the stamp endpoint.
        /// </summary>
        /// <param name="subscriptionId">The subscription identifier.</param>
        /// <param name="azureRegion">The azure region.</param>
        /// <returns>The stamp endpoint for the given account identified by <paramref name="subscriptionId"/>.</returns>
        Uri GetStampEndpoint(string subscriptionId, string azureRegion);

        /// <summary>
        /// Gets the stamp name.
        /// </summary>
        /// <param name="subscriptionId">The subscription identifier.</param>
        /// <param name="azureRegion">The azure region.</param>
        /// <returns>The stamp name for the given account identified by <paramref name="subscriptionId"/>.</returns>
        string GetStampName(string subscriptionId, string azureRegion);
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PreAggregateConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using JetBrains.Annotations;
    using Newtonsoft.Json;

    /// <summary>
    /// Represents configuration for a pre-aggregate of a metric.
    /// </summary>
    public sealed class PreAggregateConfiguration : IEquatable<PreAggregateConfiguration>
    {
        private static readonly IEqualityComparer<ICollection<string>> DimensionsEqualityComparer =
            new CollectionEqualityComparer<string>(StringComparer.OrdinalIgnoreCase);

        private static readonly IEqualityComparer<ICollection<string>> DistinctCountColumnsEqualityComparer =
            new CollectionEqualityComparer<string>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// Initializes a new instance of the <see cref="PreAggregateConfiguration"/> class.
        /// </summary>
        /// <param name="displayName">The display name.</param>
        /// <param name="dimensions">The list of dimension names for the pre-aggregate.</param>
        /// <param name="minMaxMetricsEnabled">Flag indicating whether min/max sampling types are enabled.</param>
        /// <param name="percentileMetricsEnabled">Flag indicating whether percentile sampling type is enabled.</param>
        /// <param name="distinctCountColumns">The list of dimension names for the distinct count.</param>
        [JsonConstructor]
        public PreAggregateConfiguration(string displayName, IEnumerable<string> dimensions, bool minMaxMetricsEnabled, bool percentileMetricsEnabled, IEnumerable<string> distinctCountColumns = null)
        {
            if (string.IsNullOrEmpty(displayName))
            {
                throw new ArgumentException("displayName is null or empty.");
            }

            if (dimensions == null)
            {
                throw new ArgumentNullException("dimensions");
            }

            this.DisplayName = displayName;
            this.Dimensions = dimensions.ToList();
            this.MinMaxMetricsEnabled = minMaxMetricsEnabled;
            this.PercentileMetricsEnabled = percentileMetricsEnabled;
            this.DistinctCountColumns = distinctCountColumns != null ? distinctCountColumns.ToList() : null;
        }

        /// <summary>
        /// Gets the display name.
        /// </summary>
        public string DisplayName { get; private set; }

        /// <summary>
        /// Gets the collections of dimensions which this pre-aggregate represents.
        /// </summary>
        public ICollection<string> Dimensions { get; private set; }

        /// <summary>
        /// Gets the collections of distinct count columns.
        /// </summary>
        public ICollection<string> DistinctCountColumns { get; private set; }

        /// <summary>
        /// Gets a value indicating whether min and max should be generated for the pre-aggregate.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public bool MinMaxMetricsEnabled { get; private set; }

        /// <summary>
        /// Gets a value indicating whether percentile should be generated for the pre-aggregate.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public bool PercentileMetricsEnabled { get; private set; }

        public static bool operator ==([CanBeNull] PreAggregateConfiguration left, [CanBeNull] PreAggregateConfiguration right)
        {
            return Equals(left, right);
        }

        public static bool operator !=([CanBeNull] PreAggregateConfiguration left, [CanBeNull] PreAggregateConfiguration right)
        {
            return !Equals(left, right);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
        /// </returns>
        public bool Equals(PreAggregateConfiguration other)
        {
            if (ReferenceEquals(null, other))
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            return string.Equals(this.DisplayName, other.DisplayName, StringComparison.OrdinalIgnoreCase)
                   && DimensionsEqualityComparer.Equals(this.Dimensions, other.Dimensions)
                   && DistinctCountColumnsEqualityComparer.Equals(this.DistinctCountColumns, other.DistinctCountColumns)
                   && this.MinMaxMetricsEnabled == other.MinMaxMetricsEnabled
                   && this.PercentileMetricsEnabled == other.PercentileMetricsEnabled;
        }

        /// <summary>
        /// Determines whether the specified <see cref="object" />, is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="object" /> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            var castObj = obj as PreAggregateConfiguration;
            return castObj != null
                   && this.Equals(castObj);
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            unchecked
            {
                // ReSharper disable NonReadonlyMemberInGetHashCode Justification = "All properties have a private setter used only for Json Serialization, it is effectively readonly"
                var hashCode = this.DisplayName != null
                                   ? StringComparer.OrdinalIgnoreCase.GetHashCode(this.DisplayName)
                                   : 0;
                hashCode = (hashCode * 397) ^ DimensionsEqualityComparer.GetHashCode(this.Dimensions);
                hashCode = (hashCode * 397)
                           ^ DistinctCountColumnsEqualityComparer.GetHashCode(this.DistinctCountColumns);
                hashCode = (hashCode * 397) ^ this.MinMaxMetricsEnabled.GetHashCode();
                hashCode = (hashCode * 397) ^ this.PercentileMetricsEnabled.GetHashCode();

                // ReSharper restore NonReadonlyMemberInGetHashCode
                return hashCode;
            }
        }

        /// <summary>
        /// Compares two collections for equality by using the contained items' <see cref="IEquatable{T}" /> methods
        /// </summary>
        /// <typeparam name="T">Object type.</typeparam>
        private sealed class CollectionEqualityComparer<T> : IEqualityComparer<ICollection<T>>
        {
            private readonly IEqualityComparer<T> itemComparer;

            /// <summary>
            /// Initializes a new instance of the <see cref="CollectionEqualityComparer{T}"/> class.
            /// </summary>
            /// <param name="itemComparer">The item comparer.</param>
            internal CollectionEqualityComparer([NotNull] IEqualityComparer<T> itemComparer)
            {
                this.itemComparer = itemComparer;
            }

            /// <inheritdoc />
            bool IEqualityComparer<ICollection<T>>.Equals([CanBeNull] ICollection<T> x, [CanBeNull] ICollection<T> y)
            {
                if (ReferenceEquals(x, y))
                {
                    return true;
                }

                if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
                {
                    return false;
                }

                if (x.Count != y.Count)
                {
                    return false;
                }

                int xNullCount;
                int yNullCount;
                IDictionary<T, int> xCountsDictionary = this.CreateCountDictionary(x, out xNullCount);
                IDictionary<T, int> yCountsDictionary = this.CreateCountDictionary(y, out yNullCount);

                if (xNullCount != yNullCount)
                {
                    return false;
                }

                foreach (var kvp in xCountsDictionary)
                {
                    int yValue;
                    if (!yCountsDictionary.TryGetValue(kvp.Key, out yValue))
                    {
                        return false;
                    }

                    if (kvp.Value != yValue)
                    {
                        return false;
                    }
                }

                return true;
            }

            /// <inheritdoc />
            public int GetHashCode([CanBeNull] ICollection<T> obj)
            {
                if (obj == null)
                {
                    return 0;
                }

                unchecked
                {
                    int hashCode = 0;
                    foreach (var item in obj)
                    {
                        hashCode += ReferenceEquals(item, null) ? -1 : this.itemComparer.GetHashCode(item);
                    }

                    return hashCode;
                }
            }

            [NotNull]
            private IDictionary<T, int> CreateCountDictionary([NotNull] ICollection<T> collection, out int nullCount)
            {
                // CODESYNC: Args passed to .GroupBy and .ToDictionary affect the construction of countDictionaryComparer
                Dictionary<T, int> countsDictionary = new Dictionary<T, int>(this.itemComparer);

                nullCount = 0;

                foreach (var item in collection)
                {
                    if (ReferenceEquals(item, null))
                    {
                        nullCount++;
                        continue;
                    }

                    if (!countsDictionary.ContainsKey(item))
                    {
                        countsDictionary[item] = 1;
                    }
                    else
                    {
                        countsDictionary[item]++;
                    }
                }

                return countsDictionary;
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IConfigurationUpdateResult.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// Interface to represent the configuration update result.
    /// </summary>
    public interface IConfigurationUpdateResult
    {
        /// <summary>
        /// Monitoring account on which configuration was updated.
        /// </summary>
        string MonitoringAccount { get; }

        /// <summary>
        /// True if configuration is updated successfully. False, otherwise.
        /// </summary>
        bool Success { get; set; }

        /// <summary>
        /// Exception details in case of failures.
        /// </summary>
        string Message { get; set; }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="BitBinaryReaderV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.BitHelper
{
    using System.IO;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// The class which allows reading bits from stream one by one.
    /// </summary>
    public sealed class BitBinaryReaderV2
    {
        private readonly BinaryReader reader;
        private int currentBit;
        private byte currentByte;

        /// <summary>
        /// Initializes a new instance of the <see cref="BitBinaryReaderV2"/> class.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public BitBinaryReaderV2(BinaryReader reader)
        {
            this.reader = reader;
            this.currentBit = 128;
        }

        /// <summary>
        /// Gett the index of the current bit from which data is read.
        /// </summary>
        public byte CurrentBitIndex { get; private set; }

        /// <summary>
        /// Gets the <see cref="BinaryReader"/>.
        /// </summary>
        public BinaryReader BinaryReader => this.reader;

        /// <summary>
        /// Reads bits from the stream.
        /// </summary>
        /// <param name="numBits">The number of bits.</param>
        /// <returns>
        /// Read bit.
        /// </returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public long ReadBits(int numBits)
        {
            long result = 0;
            int i;

            // First complete the current byte read
            for (i = numBits - 1; i >= 0 && this.CurrentBitIndex != 0; --i)
            {
                if (this.ReadBit())
                {
                    result |= 1L << i;
                }
            }

            // Now read byte by byte
            for (; i >= 7; i -= 8)
            {
                result |= ((long)this.GetCurrentByte()) << (i - 7);
            }

            // Now read the left bits
            for (; i >= 0; --i)
            {
                if (this.ReadBit())
                {
                    result |= 1L << i;
                }
            }

            return result;
        }

        /// <summary>
        /// Reads one bit from the stream.
        /// </summary>
        /// <returns>Read bit.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool ReadBit()
        {
            var result = (this.GetCurrentByte() & this.currentBit) != 0;
            if (this.currentBit == 1)
            {
                this.currentBit = 128;
                this.CurrentBitIndex = 0;
            }
            else
            {
                this.currentBit >>= 1;
                ++this.CurrentBitIndex;
            }

            return result;
        }

        /// <summary>
        /// Reads uint value stored in Base-128 encoding.
        /// </summary>
        /// <returns>Read value.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public uint ReadUInt32FromBase128()
        {
            return (uint)this.ReadUInt64FromBase128();
        }

        /// <summary>
        /// Reads ulong value stored in Base-128 encoding.
        /// </summary>
        /// <returns>Read value.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ulong ReadUInt64FromBase128()
        {
            ulong val = 0;
            var shift = 0;
            byte b;
            do
            {
                b = (byte)this.ReadBits(8);
                val = val + ((ulong)(b & 0x7f) << shift);
                shift += 7;
            }
            while ((b & 0x80) != 0);
            return val;
        }

        /// <summary>
        /// Reads till end of byte boundary is reached.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void ReadTillEndOfByteBoundary()
        {
            while (this.currentBit != 128)
            {
                this.ReadBit();
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private byte GetCurrentByte()
        {
            if (this.currentBit == 128)
            {
                this.currentByte = this.reader.ReadByte();
            }

            return this.currentByte;
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IDiagnosticHeartbeat.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricsExtension
{
    using System;

    /// <summary>
    /// The interface representing the diagnostic heartbeat event emitted by the MetricsExtension over ETW.
    /// </summary>
    public interface IDiagnosticHeartbeat
    {
        /// <summary>
        /// Gets the name of the MetricsExtension instance this heartbeat was emitted from.
        /// </summary>
        string InstanceName { get; }

        /// <summary>
        /// Gets the uptime in seconds.
        /// </summary>
        int UptimeInSec { get; }

        /// <summary>
        /// Gets the count of ETW events that were successfully received by the extension but could not be processed and were dropped.
        /// </summary>
        int EtwEventsDroppedCount { get; }

        /// <summary>
        /// Gets the count of ETW events that were lost prior to being recieved by the extension.
        /// </summary>
        int EtwEventsLostCount { get; }

        /// <summary>
        /// Gets the count of aggregated metrics that were dropped prior to publication.
        /// </summary>
        int AggregatedMetricsDroppedCount { get; }

        /// <summary>
        /// Gets a value indicating whether this instance is nearing reaching the maximum ETW processing queue size.
        /// </summary>
        bool IsNearingEtwQueueLimit { get; }

        /// <summary>
        /// Gets a value indicating whether this instance is nearing reaching the maximum aggregation queue limit for any of the publishers.
        /// </summary>
        bool IsNearingAggregationQueueLimit { get; }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="ClientAssemblyMigration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;

    /// <summary>
    /// Represents a single conversion from the serialized assembly to the deserialization assembly.
    /// </summary>
    internal class ClientAssemblyMigration
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ClientAssemblyMigration" /> class.
        /// </summary>
        /// <param name="fromAssembly">From assembly.</param>
        /// <param name="fromType">From type.</param>
        /// <param name="toType">To type.</param>
        public ClientAssemblyMigration(string fromAssembly, string fromType, Type toType)
        {
            this.FromAssembly = fromAssembly;
            this.FromType = fromType;
            this.ToType = toType;
        }

        /// <summary>
        /// Original assembly.
        /// </summary>
        public string FromAssembly { get; }

        /// <summary>
        /// Original type.
        /// </summary>
        public string FromType { get; }

        /// <summary>
        /// Type to bind to.
        /// </summary>
        public Type ToType { get; }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="DiagnosticHeartbeat.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricsExtension
{
    using Metrics.Etw;

    /// <summary>
    /// A class that represents the extension diagnostic heartbeat.
    /// </summary>
    internal sealed class DiagnosticHeartbeat : IDiagnosticHeartbeat
    {
        /// <summary>
        /// Gets the count of aggregated metrics that were dropped prior to publication.
        /// </summary>
        public int AggregatedMetricsDroppedCount { get; private set; }

        /// <summary>
        /// Gets the count of ETW events that were successfully received by the extension but could not be processed and were dropped.
        /// </summary>
        public int EtwEventsDroppedCount { get; private set; }

        /// <summary>
        /// Gets the count of ETW events that were lost prior to being recieved by the extension.
        /// </summary>
        public int EtwEventsLostCount { get; private set; }

        /// <summary>
        /// Gets the name of the MetricsExtension instance this heartbeat was emitted from.
        /// </summary>
        public string InstanceName { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance is nearing reaching the maximum aggregation queue limit for any of the publishers.
        /// </summary>
        public bool IsNearingAggregationQueueLimit { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance is nearing reaching the maximum ETW processing queue size.
        /// </summary>
        public bool IsNearingEtwQueueLimit { get; private set; }

        /// <summary>
        /// Gets the uptime in seconds.
        /// </summary>
        public int UptimeInSec { get; private set; }

        /// <summary>
        /// Froms the etw event.
        /// </summary>
        /// <param name="etwMetricData">The etw metric data.</param>
        /// <returns>The diagnostic heartbeat.</returns>
        public static unsafe IDiagnosticHeartbeat FromEtwEvent(NativeMethods.EventRecord* etwMetricData)
        {
            var heartbeat = new DiagnosticHeartbeat();

            var pointerInPayload = etwMetricData->UserData;
            heartbeat.InstanceName = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);

            var uptimeSec = *((int*)pointerInPayload);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(int));
            heartbeat.UptimeInSec = uptimeSec;

            var etwEventsDropped = *((int*)pointerInPayload);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(int));
            heartbeat.EtwEventsDroppedCount = etwEventsDropped;

            var etwEventsLost = *((int*)pointerInPayload);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(int));
            heartbeat.EtwEventsLostCount = etwEventsLost;

            var aggregatedMetricsDropped = *((int*)pointerInPayload);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(int));
            heartbeat.AggregatedMetricsDroppedCount = aggregatedMetricsDropped;

            var isNearingEtwLimit = *((byte*)pointerInPayload);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(byte));
            heartbeat.IsNearingEtwQueueLimit = isNearingEtwLimit != 0;

            var isNearingAggregationLimit = *((byte*)pointerInPayload);
            heartbeat.IsNearingAggregationQueueLimit = isNearingAggregationLimit != 0;

            return heartbeat;
        }
    }
}
<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="FileOperationHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.IO;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Logging;

    /// <summary>
    /// Helper class for file I/O
    /// </summary>
    internal static class FileOperationHelper
    {
        private static readonly object LogId = Logger.CreateCustomLogId("FileOperationHelper");

        /// <summary>
        /// Create folder if not existed
        /// </summary>
        /// <param name="folder">The folder</param>
        /// <returns>Whether the folder is successfully created</returns>
        internal static bool CreateFolderIfNotExists(string folder)
        {
            if (!Directory.Exists(folder))
            {
                try
                {
                    Directory.CreateDirectory(folder);
                }
                catch (Exception e)
                {
                    Logger.Log(LoggerLevel.Error, LogId, "CreateFolderIfNotExists", $"Fail to create folder {folder}. {e}");
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Save config to disk
        /// </summary>
        /// <param name="path">The path for saving the config</param>
        /// <param name="content">The content to be saved</param>
        internal static void SaveContentToFile(string path, string content)
        {
            try
            {
                File.WriteAllText(path, content);
            }
            catch (Exception e)
            {
                Logger.Log(LoggerLevel.Error, LogId, "SaveConfig", $"Fail to save file {path}. {e}");
                throw;
            }
        }

        /// <summary>
        /// Async save config to disk
        /// </summary>
        /// <param name="path">The path for saving the config</param>
        /// <param name="content">The content to be saved</param>
        /// <returns>return a Task</returns>
        internal static async Task SaveContentToFileAsync(string path, string content)
        {
            try
            {
                using (StreamWriter writer = new StreamWriter(path, false))
                {
                    await writer.WriteAsync(content).ConfigureAwait(false);
                }
            }
            catch (Exception e)
            {
                Logger.Log(LoggerLevel.Error, LogId, "SaveContentToFileAsync", $"Fail to save file {path}. {e}");
                throw;
            }
        }
    }
}<file_sep>﻿// <copyright file="IPoolTrackable.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

namespace Metrics.Services.Common
{
    /// <summary>
    /// The state of the poolable object
    /// </summary>
    public enum PoolObjectTrackingInfo : byte
    {
        /// <summary>
        /// The object does not support allocation/release tracking
        /// </summary>
        TrackingNotSupported = 0,

        /// <summary>
        /// The object is currently owned by the pool and was not allocated to the user code
        /// </summary>
        InPool = 1,

        /// <summary>
        /// The object is currently allocated from the pool and is owned by the user code
        /// </summary>
        Allocated = 2,

        /// <summary>
        /// The object was instantiated but the value was not set.
        /// TODO: this should have the value of 0 (and TrackingNotSupported should be 3) when unit tests that return the object to pool without
        /// allocating it from pool are fixed - this way we can catch the NotSet value during release for cases of objects being
        /// returned to pool without being allocated from the pool
        /// </summary>
        NotSet = 3,
    }

    /// <summary>
    /// All poolable objects must implement this interface to
    /// enable object tracking by pool code
    /// </summary>
    public interface IPoolTrackable
    {
        /// <summary>
        /// Gets or sets the value indicating whether this object is currently owned by a pool.
        /// This property is set by object pool code.
        /// Initial value of the property is PoolObjectTrackingInfo.TrackingNotSupported
        /// </summary>
        PoolObjectTrackingInfo PoolObjectTrackingInfo { get; set; }
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="PercentileConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// Determine if the percentile sampling types are available for this preaggregate.
    /// </summary>
    public sealed class PercentileConfiguration : IPercentileConfiguration
    {
        /// <summary>
        /// PercentileConfiguration where percentile sampling types are enabled.
        /// </summary>
        public static readonly PercentileConfiguration PercentileEnabled = new PercentileConfiguration(true);

        /// <summary>
        /// PercentileConfiguration where percentile sampling types are disabled.
        /// </summary>
        public static readonly PercentileConfiguration PercentileDisabled = new PercentileConfiguration(false);

        /// <summary>
        /// Initializes a new instance of the <see cref="PercentileConfiguration"/> class.
        /// </summary>
        /// <param name="enabled">Whether or not the feature is enabled.</param>
        [JsonConstructor]
        internal PercentileConfiguration(bool enabled)
        {
            this.Enabled = enabled;
        }

        /// <summary>
        /// Determines if the percentile sampling types are enabled or disabled.
        /// </summary>
        public bool Enabled { get; }
    }
}
<file_sep>// -----------------------------------------------------------------------
// <copyright file="AssemblyInfo.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Reflection;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1016:MarkAssembliesWithAssemblyVersion", Justification = "Assembly Version not required.")]
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IExpression.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// An expression that can be evaluated for computed sampling types or composite metrics.
    /// </summary>
    public interface IExpression
    {
        /// <summary>
        /// Gets or sets the name of the expression.
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// Gets or sets the expression.
        /// </summary>
        string Expression { get; set; }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="QueryResult.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System.Collections.Generic;
    using System.Text;
    using Newtonsoft.Json;

    /// <summary>
    /// Represents a single time series that was selected and is being returned as a query result.
    /// </summary>
    internal sealed class QueryResult : IQueryResult
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="QueryResult"/> class.
        /// Create a single query result
        /// </summary>
        /// <param name="dimensionList">The list of dimensions.</param>
        /// <param name="evaluatedResult">The evaluated result.</param>
        /// <param name="seriesValues">The time series values, only included if specified in the query.</param>
        [JsonConstructor]
        internal QueryResult(
            KeyValuePair<string, string>[] dimensionList,
            double? evaluatedResult,
            double?[] seriesValues)
        {
            this.DimensionList = dimensionList;
            this.EvaluatedResult = evaluatedResult;
            this.TimeSeries = seriesValues;
        }

        /// <summary>
        /// Set of valid dimension name-value pairs that meet the query condition.
        /// </summary>
        public IReadOnlyList<KeyValuePair<string, string>> DimensionList { get; private set; }

        /// <summary>
        /// Gets the evaluated value for this time series that meets the condition set in the query (provided for evidence and/or sorting).
        /// </summary>
        public double? EvaluatedResult { get; private set; }

        /// <summary>
        /// Gets the full collection time series values for the query interval. It should be null if
        /// the query did not request the full collection of values to be returned.
        /// </summary>
        public double?[] TimeSeries { get; private set; }

        /// <summary>
        /// Returns a <see cref="string" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="string" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine(string.Format("Result: {0}", this.EvaluatedResult));
            sb.Append("Dimensions:");
            foreach (var pair in this.DimensionList)
            {
                sb.Append(string.Format("{0}: {1};", pair.Key, pair.Value));
            }

            sb.AppendLine();
            if (this.TimeSeries != null && this.TimeSeries.Length > 0)
            {
                sb.Append("[");
                sb.Append(string.Join(", ", this.TimeSeries));
                sb.AppendLine("]");
            }

            return sb.ToString();
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IFilteredTimeSeriesQueryResponse.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// An interface for filtered time series query response.
    /// </summary>
    public interface IFilteredTimeSeriesQueryResponse
    {
        /// <summary>
        /// Gets the query request.
        /// </summary>
        FilteredTimeSeriesQueryRequest QueryRequest { get; }

        /// <summary>
        /// Gets the start time in UTC for the query results.
        /// </summary>
        DateTime StartTimeUtc { get; }

        /// <summary>
        /// Gets the end time in UTC for the query results.
        /// </summary>
        DateTime EndTimeUtc { get; }

        /// <summary>
        /// Gets the time resolution in milliseconds for the query results.
        /// </summary>
        int TimeResolutionInMinutes { get; }

        /// <summary>
        /// Gets the <see cref="FilteredTimeSeries"/> list. Each item represents a single time series where start time, end time and time resolution
        /// is represented by this object members.
        /// </summary>
        IReadOnlyList<IFilteredTimeSeries> FilteredTimeSeriesList { get; }

        /// <summary>
        /// Gets the error code.
        /// </summary>
        FilteredTimeSeriesQueryResponseErrorCode ErrorCode { get; }

        /// <summary>
        /// Gets the diagnostics information.
        /// </summary>
        DiagnosticInfo DiagnosticInfo { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SerializationConstants.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    /// <summary>
    /// Serialization constants.
    /// </summary>
    public static class SerializationConstants
    {
        /// <summary>
        /// The default series resolution in minutes.
        /// </summary>
        public const int DefaultSeriesResolutionInMinutes = 1;

        /// <summary>
        /// The http header indicating if we should skip scaling the metrics data.
        /// </summary>
        public const string ScalingFactorDisabledHeader = "__ScalingFactorDisabled__";

        /// <summary>
        /// The MIME type for octet streams.
        /// </summary>
        public const string OctetStreamContentType = "application/octet-stream";

        /// <summary>
        /// The trace identifier header.
        /// </summary>
        public const string TraceIdHeader = "TraceGuid";

        /// <summary>
        /// The client identifier header.
        /// </summary>
        public const string ClientIdHeader = "ClientId";

        /// <summary>
        /// The maximum metric description length allowed.
        /// </summary>
        public const int MaximumMetricDescriptionLength = 1024;

        /// <summary>
        /// The maximum description length allowed.
        /// </summary>
        public const int MaximumDescriptionLength = 256;
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="BitBinaryWriter.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.BitHelper
{
    using System.IO;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// A class which allows writing bits to the stream.
    /// We accumulate current byte value bit by bit and when it is full, it is written to the stream.
    /// <see cref="Flush" /> method must be called at the end since the last byte to write to stream could be partial.
    /// </summary>
    public sealed class BitBinaryWriter
    {
        private const int HighestBitInByte = 1 << 7;
        private readonly BinaryWriter writer;
        private int currentBit;

        /// <summary>
        /// Initializes a new instance of the <see cref="BitBinaryWriter"/> class.
        /// </summary>
        /// <param name="writer">The writer.</param>
        public BitBinaryWriter(BinaryWriter writer)
        {
            this.writer = writer;

            this.currentBit = HighestBitInByte;
            this.CurrentByte = 0;
        }

        /// <summary>
        /// Gets the value of the currently accumulated byte.
        /// </summary>
        public byte CurrentByte { get; private set; }

        /// <summary>
        /// Gets the underlying binary writer.
        /// </summary>
        public BinaryWriter BinaryWriter => this.writer;

        /// <summary>
        /// Write bits to the stream.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="numBits">The number bits.</param>
        /// <param name="positionOfLeastSignificantBit">The position of least significant bit, starting with 0.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteBits(long value, int numBits, int positionOfLeastSignificantBit)
        {
            for (int i = numBits; i > 0; --i)
            {
                long mask = 1L << (i - 1 + positionOfLeastSignificantBit);

                bool bit = (value & mask) != 0;
                this.CurrentByte = (byte)(this.CurrentByte | (bit ? this.currentBit : 0));

                this.currentBit >>= 1;

                if (this.currentBit == 0)
                {
                    this.writer.Write(this.CurrentByte);
                    this.CurrentByte = 0;
                    this.currentBit = HighestBitInByte;
                }
            }
        }

        /// <summary>
        /// Write bit to the stream.
        /// </summary>
        /// <param name="bit">Bit value to write, where True means 1 and False means 0.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteBit(bool bit)
        {
            this.CurrentByte = (byte)(this.CurrentByte | (bit ? this.currentBit : 0));

            this.currentBit >>= 1;

            if (this.currentBit == 0)
            {
                this.writer.Write(this.CurrentByte);
                this.CurrentByte = 0;
                this.currentBit = HighestBitInByte;
            }
        }

        /// <summary>
        /// Flush the current byte into stream even if it is partial.
        /// </summary>
        public void Flush()
        {
            if (this.currentBit != HighestBitInByte)
            {
                this.writer.Write(this.CurrentByte);
                this.CurrentByte = 0;
                this.currentBit = HighestBitInByte;
            }
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IPreaggregation.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;

    /// <summary>
    /// A grouping of dimensions used to aggregate metric data.
    /// </summary>
    public interface IPreaggregation
    {
        /// <summary>
        /// Gets or sets the name of the preaggregate.
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// Gets the dimensions of the preaggregate in sorted order..
        /// </summary>
        IEnumerable<string> Dimensions { get; }

        /// <summary>
        /// The min/max sampling type configuration.
        /// </summary>
        IMinMaxConfiguration MinMaxConfiguration { get; set; }

        /// <summary>
        /// The percentile sampling type configuration.
        /// </summary>
        IPercentileConfiguration PercentileConfiguration { get; set; }

        /// <summary>
        /// The data rollup configuration.
        /// </summary>
        IRollupConfiguration RollupConfiguration { get; set; }

        /// <summary>
        /// The metric data store configuration.
        /// </summary>
        IPublicationConfiguration PublicationConfiguration { get; set; }

        /// <summary>
        /// The distinct count sampling type configuration.
        /// </summary>
        IDistinctCountConfiguration DistinctCountConfiguration { get; set; }

        /// <summary>
        /// The filtering configuration.
        /// </summary>
        IFilteringConfiguration FilteringConfiguration { get; set; }

        /// <summary>
        /// Adds the dimension to the preaggregate.
        /// </summary>
        /// <param name="dimensionName">Name of the dimension.</param>
        void AddDimension(string dimensionName);

        /// <summary>
        /// Removes the dimension from the preaggregate.
        /// </summary>
        /// <param name="dimensionName">Name of the dimension.</param>
        void RemoveDimension(string dimensionName);
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="ICompositeMetricConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;

    /// <summary>
    /// Represents a composite metric in the MDM System.
    /// </summary>
    public interface ICompositeMetricConfiguration : IMetricConfiguration
    {
        /// <summary>
        /// Gets the metric sources.
        /// </summary>
        IEnumerable<CompositeMetricSource> MetricSources { get; }

        /// <summary>
        /// Gets the composite expressions.
        /// </summary>
        IEnumerable<CompositeExpression> CompositeExpressions { get; }

        /// <summary>
        /// Gets or sets a value indicating whether to treat missing series as zeroes.
        /// </summary>
        bool TreatMissingSeriesAsZeroes { get; set; }

        /// <summary>
        /// Adds the metric source.
        /// </summary>
        /// <param name="metricSource">The metric source.</param>
        void AddMetricSource(CompositeMetricSource metricSource);

        /// <summary>
        /// Removes the metric source.
        /// </summary>
        /// <param name="metricSourceName">The metric source name.</param>
        void RemoveMetricSource(string metricSourceName);

        /// <summary>
        /// Adds the expression.
        /// </summary>
        /// <param name="expression">The expression.</param>
        void AddExpression(CompositeExpression expression);

        /// <summary>
        /// Removes the expression.
        /// </summary>
        /// <param name="expressionName">The expression name.</param>
        void RemoveExpression(string expressionName);
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="MetricConfigurationV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using Newtonsoft.Json;

    /// <summary>
    /// The configuration used by MetricsExtension.  It will only contain the necessary
    /// fields needed by the client.
    /// </summary>
    public struct MetricConfigurationV2 : IEquatable<MetricConfigurationV2>
    {
        /// <summary>
        /// The name of the metric used for pumping data for <see cref="SamplingTypes.Count"/>
        /// in the new (Monitoring Agent) pipeline.
        /// </summary>
        public const string CountMetricName = "Count";

        /// <summary>
        /// The name of the metric used for pumping data for <see cref="SamplingTypes.Sum"/>
        /// in the new (Monitoring Agent) pipeline.
        /// </summary>
        /// <remarks>
        /// This is the metric contains configuration indicating whether <see cref="SamplingTypes.Max"/>,
        /// <see cref="SamplingTypes.Min"/>, <see cref="Histogram"/> etc.. are enabled.
        /// </remarks>
        public const string SumMetricName = "Sum";

        /// <summary>
        /// Gets the default configuration to be used, when the configuration is not obtained from server.
        /// </summary>
        public const SamplingTypes DefaultSamplingTypes = SamplingTypes.Count | SamplingTypes.Sum;

        /// <summary>
        /// Gets or sets the collections of dimensions configured for Distinct count.
        /// </summary>
        [JsonProperty("pc")]
        public List<PreAggregateConfiguration> PreAggregationsWithDistinctCountColumns;

        /// <summary>
        /// The map of "special" strings in metric names to default scaling factors to be used when scaling factor is not configured for metrics.
        /// </summary>
        /// <remarks>
        /// The values for scaling factor in this list must be greater than or equal to zero.
        /// </remarks>
        private static readonly List<KeyValuePair<string, float>> DefaultScalingFactorForMetrics =
            new List<KeyValuePair<string, float>> { new KeyValuePair<string, float>("%", 100f), };

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricConfigurationV2"/> struct.
        /// </summary>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="scalingFactor">The scaling factor.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        public MetricConfigurationV2(string metricName, float? scalingFactor, SamplingTypes samplingTypes)
            : this()
        {
            this.MetricName = metricName;
            this.ScalingFactor = scalingFactor;
            this.SamplingTypes = samplingTypes;

            this.EnableClientSideForking = true;
            this.EnableClientSidePublication = true;
            this.EnableClientSideEtwPublication = true;
            this.IngestionOptions = 0;
            this.HyperLogLogBValue = HyperLogLogSketch.DefaultBValue;
        }

        /// <summary>
        /// Gets or sets the version of this instance.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public uint Version { get; set; }

        /// <summary>
        /// Gets or sets the Monitoring Account to which this metric is reported.
        /// </summary>
        [JsonProperty(PropertyName = "ma", DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string MonitoringAccount { get; set; }

        /// <summary>
        /// Gets or sets the metric namespace.
        /// </summary>
        [JsonProperty(PropertyName = "ns", DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string MetricNamespace { get; set; }

        /// <summary>
        /// Gets or sets the metric name.
        /// </summary>
        [JsonProperty(PropertyName = "m")]
        public string MetricName { get; set; }

        /// <summary>
        /// Gets the metric name and sampling types.
        /// </summary>
        [JsonProperty(PropertyName = "s", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        [DefaultValue(DefaultSamplingTypes)]
        public SamplingTypes SamplingTypes { get; set; }

        /// <summary>
        /// Gets or sets the scaling factor to be used.
        /// </summary>
        /// <remarks>
        /// Clients should pass un-scaled values when this is null.
        /// </remarks>
        [JsonProperty(PropertyName = "sf", DefaultValueHandling = DefaultValueHandling.Ignore)]
        public float? ScalingFactor { get; set; }

        /// <summary>
        /// Gets or sets the collections of dimensions configured for Distinct count.
        /// </summary>
        /// <remarks>
        /// Each HyperLogLog counter uses a small, fixed amount of space but can estimate
        /// the cardinality of any set of up to around a billion values with relative error
        /// of 1.04 / Math.sqrt(2 ** b) with high probability.
        /// </remarks>
        [JsonProperty("bv")]
        public int HyperLogLogBValue { get; set; }

        /// <summary>
        /// Gets or sets the list of dimension configurations.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public IList<DimensionConfiguration> DimensionConfigurations { get; set; }

        /// <summary>
        /// Gets or sets the list of pre-aggregates which this metric will be aggregated with.
        /// </summary>
        /// <remarks>
        /// We have hidden the configuration APIs.
        /// If we ever need to expose the configuration APIs, we need to revisit the types of properties.
        /// For this particular property, we can expose a strong-typed class named PreAggregateConfigurationList that can check for duplicate entries,
        /// or we can even make the whole configuration object read only.
        /// </remarks>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public List<PreAggregateConfiguration> PreAggregations { get; set; }

        /// <summary>
        /// Gets or sets the computed sampling types.
        /// In the case of <see cref="IsCompositeMetric"/>, this field is used to save the expressions and their names.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public IList<ComputedSamplingTypeConfiguration> ComputedSamplingTypes { get; set; }

        /// <summary>
        /// Gets or sets the list of the variable names of metrics to the metric identifiers.
        /// The keys can be used in the <see cref="ComputedSamplingTypeConfiguration.Expression"/> field of the <see cref="ComputedSamplingTypes"/>.
        /// </summary>
        /// <remarks>
        /// Used only for composite metrics.
        /// Composite metric cannot use <see cref="MetricIdentifier"/> of another composite metric as of now, so the UI should show hints appropriately.
        /// </remarks>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public Dictionary<string, MetricIdentifier> MetricIdentifiers { get; set; }

        /// <summary>
        /// Gets a value indicating whether the metric configuration is for a composite metric (i.e. one that is composed of multiple metrics).
        /// </summary>
        [JsonIgnore]
        public bool IsCompositeMetric
        {
            get { return this.MetricIdentifiers != null && this.MetricIdentifiers.Count > 0; }
        }

        /// <summary>
        /// Gets or sets whether this event should be published by the metrics extension to any of the account defined endpoints.
        /// </summary>
        /// <remarks>
        /// If false, publication will not occur to the FE, but it will still be produced as a local aggregate on the client machine for Cosmos upload.
        /// </remarks>
        [JsonProperty("pe", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        [DefaultValue(true)]
        public bool EnableClientSidePublication { get; set; }

        /// <summary>
        /// Gets or sets whether this event should be published by the metrics extension to any of the external account endpoints.
        /// </summary>
        /// <remarks>
        /// If false, publication will still occur to the MDM primary endpoint but not to any of the external/forking endpoints.
        /// </remarks>
        [JsonProperty("fe", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        [DefaultValue(true)]
        public bool EnableClientSideForking { get; set; }

        /// <summary>
        /// Gets or sets whether this event should be published to the aggregated ETW provider.
        /// </summary>
        [JsonProperty("epe", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        [DefaultValue(true)]
        public bool EnableClientSideEtwPublication { get; set; }

        /// <summary>
        /// Gets or sets ingestions options to be used for this metric.
        /// </summary>
        /// <remarks>
        /// If false, publication will still occur to the MDM primary endpoint but not to any of the external/forking endpoints.
        /// </remarks>
        [JsonProperty("igo", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        [DefaultValue(true)]
        public short IngestionOptions { get; set; }

        /// <summary>
        /// Gets or sets whether metrics in composite metrics should be considered as zero when metric is not reported or is missing.
        /// </summary>
        /// <remarks>
        /// Only used for Composite metrics.
        /// </remarks>
        [JsonProperty("tz", DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
        [DefaultValue(false)]
        public bool TreatMissingSeriesAsZeros { get; set; }

        /// <summary>
        /// Gets the default scaling factor if <paramref name="metric" /> contains scaling indicators.
        /// </summary>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metric">The metric name.</param>
        /// <returns>
        /// The default scaling factor.
        /// </returns>
        public static float? GetDefaultScalingFactor(string metricNamespace, string metric)
        {
            float? defaultScalingFactor = null;

            foreach (var entry in DefaultScalingFactorForMetrics)
            {
                if (metric.Contains(entry.Key))
                {
                    defaultScalingFactor = entry.Value;
                }
            }

            return defaultScalingFactor;
        }

        /// <summary>
        /// Determines whether an instance is equal to this instance.
        /// </summary>
        /// <param name="other">Instance to compare to this instance.</param>
        /// <returns>
        /// true if the specified objects are equal; otherwise, false.
        /// </returns>
        public bool Equals(MetricConfigurationV2 other)
        {
            if (other.MetricName == null)
            {
                if (this.MetricName == null)
                {
                    return true;
                }

                return false;
            }

            if (this.MetricName == null)
            {
                return false;
            }

            return StringComparer.OrdinalIgnoreCase.Equals(this.MetricName, other.MetricName);
        }

        /// <summary>
        /// Gets hashcode.
        /// </summary>
        /// <returns>Hash code.</returns>
        public override int GetHashCode()
        {
            return StringComparer.OrdinalIgnoreCase.GetHashCode(this.MetricName);
        }

        /// <summary>
        /// Overrides Object.Equals.
        /// </summary>
        /// <param name="obj">Object to compare with.</param>
        /// <returns>True if equals, false otherwise.</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is MetricConfigurationV2))
            {
                return false;
            }

            return this.Equals((MetricConfigurationV2)obj);
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="BitAggregateMagic.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.BitHelper
{
    using System.Runtime.CompilerServices;

    /// <summary>
    /// The Aggregate Magic Algorithms adapted from @ http://aggregate.org/MAGIC/
    /// </summary>
    public static class BitAggregateMagic
    {
        /// <summary>
        /// The number of bits in long integer.
        /// </summary>
        public const byte NumBitsInLongInteger = 64;

        /// <summary>
        /// Counts the number of set bits in <paramref name="x"/>.
        /// </summary>
        /// <param name="x">The target for which to count the number of set bits.</param>
        /// <returns>The number of set bits in <paramref name="x"/>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int CountOneBits(ulong x)
        {
            x = (x & 0x5555555555555555) + ((x >> 1) & 0x5555555555555555);
            x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
            x = (x & 0x0f0f0f0f0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f0f0f0f0f);
            x = (x & 0x00ff00ff00ff00ff) + ((x >> 8) & 0x00ff00ff00ff00ff);
            x = (x & 0x0000ffff0000ffff) + ((x >> 16) & 0x0000ffff0000ffff);
            x = (x & 0x00000000ffffffff) + ((x >> 32) & 0x00000000ffffffff);

            return (int)x;
        }

        /// <summary>
        /// Counts the number of leading zeros.
        /// </summary>
        /// <param name="x">The target for which to count the number of leading zeros.</param>
        /// <returns>The number of leading zeros.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int CountLeadingZeros(long x)
        {
            if (x < 0)
            {
                return 0;
            }

            x |= x >> 1;
            x |= x >> 2;
            x |= x >> 4;
            x |= x >> 8;
            x |= x >> 16;
            x |= x >> 32;

            return NumBitsInLongInteger - CountOneBits((ulong)x);
        }

        /// <summary>
        /// Counts the number of trailing zeros.
        /// </summary>
        /// <param name="x">The target for which to count the number of trailing zeros.</param>
        /// <returns>The number of trailing zeros.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int CountTrailingZeros(long x)
        {
            return CountOneBits((ulong)(x & -x) - 1);
        }

        /// <summary>
        /// Given a binary integer value x, the next largest power of 2 can be computed by a SWAR
        /// algorithm that recursively "folds" the upper bits into the lower bits.
        /// This process yields a bit vector with the same most significant 1 as x, but all 1's below it.
        /// Adding 1 to that value yields the next largest power of 2.
        /// </summary>
        /// <param name="x">The value for which the next largest power of 2 is needed</param>
        /// <returns>The next largest power of 2 for x which is strictly more than x
        /// Note that for x = 32 the result is 64, not 32.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int NextLargestPowerOf2(int x)
        {
            x |= x >> 1;
            x |= x >> 2;
            x |= x >> 4;
            x |= x >> 8;
            x |= x >> 16;
            return x + 1;
        }

        /// <summary>
        /// Checks whether x is a power of two.
        /// </summary>
        /// <param name="x">The number to check,</param>
        /// <returns>true if x is a power of 2, false otherwise. (Note that 0 is a power of 2)</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsPowerOfTwo(ulong x)
        {
            return (x & (x - 1)) == 0;
        }

        /// <summary>
        /// Changes uinsigned int endian encoding.
        /// Can be used both ways: BE->LE and LE->BE.
        /// </summary>
        /// <param name="x">Value to change.</param>
        /// <returns>Value with changed endian encoding.</returns>
        public static uint ChangeEndianness(uint x)
        {
            // swap adjacent 16-bit blocks
            x = (x >> 16) | (x << 16);

            // swap adjacent 8-bit blocks
            return ((x & 0xFF00FF00) >> 8) | ((x & 0x00FF00FF) << 8);
        }

        /// <summary>
        /// Changes uinsigned long endian encoding.
        /// Can be used both ways: BE->LE and LE->BE.
        /// </summary>
        /// <param name="x">Value to change.</param>
        /// <returns>Value with changed endian encoding.</returns>
        public static ulong ChangeEndianness(ulong x)
        {
            // swap adjacent 32-bit blocks
            x = (x >> 32) | (x << 32);

            // swap adjacent 16-bit blocks
            x = ((x & 0xFFFF0000FFFF0000) >> 16) | ((x & 0x0000FFFF0000FFFF) << 16);

            // swap adjacent 8-bit blocks
            return ((x & 0xFF00FF00FF00FF00) >> 8) | ((x & 0x00FF00FF00FF00FF) << 8);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EtlBacklogConfig.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
// Type that contains the information describing how backlog of ETL files should be processed.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;

    /// <summary>
    /// Type that contains the information describing how backlog of ETL files should be processed.
    /// </summary>
    internal struct EtlBacklogConfig
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="EtlBacklogConfig"/> struct.
        /// </summary>
        /// <param name="shouldReceiveBacklogFiles">
        /// A boolean indicating whether the subscriber should receive any backlog of ETL files.
        /// </param>
        /// <param name="backlogTargetStartTime">
        /// The point in time from which the subscriber wants to receive backlog files.
        /// </param>
        /// <param name="maxBacklogFilesRequested">
        /// The maximum number of backlog files that are going to be passed to the subscriber.
        /// </param>
        public EtlBacklogConfig(bool shouldReceiveBacklogFiles, DateTime backlogTargetStartTime, int maxBacklogFilesRequested)
            : this()
        {
            this.ShouldReceiveBacklogFiles = shouldReceiveBacklogFiles;
            this.TargetStartTimeUtc = backlogTargetStartTime;
            this.MaxFiles = maxBacklogFilesRequested;
        }

        /// <summary>
        /// Gets a value indicating whether the subscriber should receive any backlog of ETL files.
        /// </summary>
        public bool ShouldReceiveBacklogFiles { get; private set; }

        /// <summary>
        /// Gets the point in time from which the subscriber wants to receive backlog files.
        /// </summary>
        /// <remarks>
        /// Since this point in time is likely contained in the middle of an ETL file and the
        /// dispatchers do not drop events prior to this time on behalf of the subscriber, the
        /// subscriber itself should be prepared to receive and ignore events prior to this point
        /// in time.
        /// </remarks>
        /// <remarks>
        /// If the number of backlog files to be processed from this point in time exceeds the
        /// <see cref="MaxFiles"/> the latter has priority and only that number of backlog files
        /// are going to be sent to the subscriber.
        /// </remarks>
        public DateTime TargetStartTimeUtc { get; private set; }

        /// <summary>
        /// Gets the maximum number of backlog files that are going to be passed to the subscriber.
        /// This is one way to put some kind of upper bound on the amount of work triggered by backlog
        /// files.
        /// </summary>
        /// <remarks>
        /// Notice how this value relates to the <see cref="TargetStartTimeUtc"/>.
        /// </remarks>
        public int MaxFiles { get; private set; }
    }
}<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="OperationStatus.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// Operation result status Type.
    /// </summary>
    public enum OperationStatus
    {
        CompleteSuccess = 0,
        ResourceNotFound = 1,
        ConnectionError = 2,
        FolderCreationError = 3,
        FileSaveError = 4,
        ResourceGetError = 5,
        ResourcePostError = 6,
        ResourceSkipped = 7,
        FileCorrupted = 8
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricEnrichmentRuleTransformationDefinition.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricEnrichmentRuleManagement
{
    using System.Collections.Generic;

    /// <summary>
    /// Represents a tranformation definition to be applied to the metric event during metric data enrichment.
    /// </summary>
    public sealed class MetricEnrichmentRuleTransformationDefinition
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MetricEnrichmentRuleTransformationDefinition"/> class.
        /// </summary>
        /// <param name="executorId">The executor id to be used for transformation. Example: IdMapping based Addition Executor.</param>
        /// <param name="sourceEventDimensionNamesForKey">The source event dimension names the dimension names whose value will be used for building the key for lookup table in external data service.</param>
        /// <param name="executorConnectionStringOverride">The executor connection string override for executor connection string.</param>
        /// <param name="transformationType">The type of transformation the rule represents..</param>
        /// <param name="destinationColumnNamesForDimensions">
        /// The map where Key represents destination column to be used to extract dimension values
        /// and Value represents the final dimension to be used for the destination column value.
        /// </param>
        public MetricEnrichmentRuleTransformationDefinition(
            string executorId,
            List<string> sourceEventDimensionNamesForKey,
            string executorConnectionStringOverride,
            MetricEnrichmentTransformationType transformationType,
            Dictionary<string, string> destinationColumnNamesForDimensions)
        {
            this.ExecutorId = executorId;
            this.SourceEventDimensionNamesForKey = sourceEventDimensionNamesForKey;
            this.ExecutorConnectionStringOverride = executorConnectionStringOverride;
            this.TransformationType = transformationType;
            this.DestinationColumnNamesForDimensions = destinationColumnNamesForDimensions;
        }

        /// <summary>
        /// Gets or sets the executor id to be used for transformation. Example: IdMapping based Addition Executor.
        /// </summary>
        public string ExecutorId { get; }

        /// <summary>
        /// Gets or sets the dimension names whose value will be used for building the key for lookup table in external data service.
        /// </summary>
        public List<string> SourceEventDimensionNamesForKey { get; }

        /// <summary>
        /// Gets or sets the override for executor connection string.
        /// </summary>
        public string ExecutorConnectionStringOverride { get; }

        /// <summary>
        /// Represents the type of transformation the rule represents.
        /// </summary>
        public MetricEnrichmentTransformationType TransformationType { get; }

        /// <summary>
        /// Gets or sets the map
        /// where Key represents destination column to be used to extract dimension values
        /// and Value represents the final dimension to be used for the destination column value.
        /// </summary>
        public Dictionary<string, string> DestinationColumnNamesForDimensions { get; }

        /// <summary>
        /// Validates the data is valid rule.
        /// </summary>
        /// <returns>
        /// Validation failure message, empty means validation passed.
        /// </returns>
        internal string Validate()
        {
            if (string.IsNullOrEmpty(this.ExecutorId))
            {
                // TODO: Once executor's are finalized add executor id validation too.
                return "Executor id cannot be null";
            }

            return string.Empty;
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IComputedSamplingTypeExpression.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// An expression that can be evaluated for computed sampling types or composite metrics.
    /// </summary>
    public interface IComputedSamplingTypeExpression : IExpression
    {
        /// <summary>
        /// Gets a value indicating whether this instance is built in.
        /// </summary>
        bool IsBuiltIn { get; }

        /// <summary>
        /// Gets or sets the unit.
        /// </summary>
        string Unit { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UserAccessTokenRefresher.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Logging;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;

    /// <summary>
    /// A helper class to retrieve and refresh the user access token.
    /// </summary>
    public sealed class UserAccessTokenRefresher
    {
        /// <summary>
        /// The bearer token authentication scheme.
        /// </summary>
        public const string BearerTokenAuthScheme = "Bearer";
        private const string Authority = "https://login.microsoftonline.com/microsoft.onmicrosoft.com";
        private const string TargetResource = "http://GenevaMDM/";
        private const string ClientId = "8434f70a-4f5a-4dbe-8c83-c8ca8029ea22";
        private static readonly object LogId = Logger.CreateCustomLogId("UserAccessTokenRefresher");
        private static Lazy<UserAccessTokenRefresher> instance = new Lazy<UserAccessTokenRefresher>(() => new UserAccessTokenRefresher());
        private readonly Uri clientRedirectUri = new Uri("http://GenevaMDMClient");
        private readonly SemaphoreSlim accessTokenRefreshLock = new SemaphoreSlim(1);
        private string userAccessToken;
        private DateTime lastAccessTokenRefreshTime;

        /// <summary>
        /// Gets the instance.
        /// </summary>
        internal static UserAccessTokenRefresher Instance
        {
            get
            {
                return instance.Value;
            }
        }

        /// <summary>
        /// Gets the user access token.
        /// </summary>
        internal string UserAccessToken
        {
            get
            {
                if (this.userAccessToken == null)
                {
                    this.RefreshAccessToken().Wait();

                    if (this.userAccessToken == null)
                    {
                        throw new MetricsClientException("Failed to obtain an AAD user access token.");
                    }
                }

                return this.userAccessToken;
            }
        }

        /// <summary>
        /// Refreshes the access token.
        /// </summary>
        /// <returns>A <see cref="Task"/> representing the execution.</returns>
        internal async Task RefreshAccessToken()
        {
            await this.accessTokenRefreshLock.WaitAsync();
            try
            {
                // 10-minute is an arbitrary age and we try to request the AAD token only once when multiple HTTP clients try to renew the token at the same time.
                if (this.lastAccessTokenRefreshTime < DateTime.UtcNow.AddMinutes(-10))
                {
                    var authContext = new AuthenticationContext(Authority);
                    var result =
                        await authContext.AcquireTokenAsync(TargetResource, ClientId, this.clientRedirectUri, new PlatformParameters()).ConfigureAwait(false);
                    this.userAccessToken = result.AccessToken;
                    this.lastAccessTokenRefreshTime = DateTime.UtcNow;

                    Logger.Log(LoggerLevel.Info, LogId, "RefreshAccessToken", "Succeeded");
                }
            }
            catch (Exception e)
            {
                Logger.Log(LoggerLevel.Error, LogId, "RefreshAccessToken", e.ToString());
                throw;
            }
            finally
            {
                this.accessTokenRefreshLock.Release();
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StampLevelMetricEnrichmentRuleManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricEnrichmentRuleManagement
{
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using MetricEnrichmentRuleManagement;
    using Microsoft.Cloud.Metrics.Client.Utility;

    using Newtonsoft.Json;

    /// <summary>
    /// This class manages get and save operations on stamp level metric enrichment rules ( only service admins are authorized to modify stamp level rules).
    /// </summary>
    public sealed class StampLevelMetricEnrichmentRuleManager
    {
        private readonly ConnectionInfo connectionInfo;
        private readonly HttpClient httpClient;
        private readonly string configurationUrlPrefix;
        private readonly JsonSerializerSettings serializerSettings;

        /// <summary>
        /// Initializes a new instance of the <see cref="StampLevelMetricEnrichmentRuleManager"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the MDM endpoint being used.</param>
        public StampLevelMetricEnrichmentRuleManager(ConnectionInfo connectionInfo)
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            this.connectionInfo = connectionInfo;
            this.configurationUrlPrefix = this.connectionInfo.GetAuthRelativeUrl("v1/config/enrichmentrules/");

            this.httpClient = HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo);

            this.serializerSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto
            };
        }

        /// <summary>
        /// Gets all the enrichment rules configured for given monitoring account.
        /// </summary>
        /// <returns>All enrichment rules for the given monitoring account.</returns>
        public async Task<IReadOnlyList<MetricEnrichmentRule>> GetAllAsync()
        {
            var path = $"{this.configurationUrlPrefix}getAll";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(string.Empty))
            {
                Path = path
            };

            var response = await HttpClientHelper.GetResponseAsStringAsync(
                uriBuilder.Uri,
                HttpMethod.Get,
                this.httpClient,
                string.Empty,
                this.configurationUrlPrefix).ConfigureAwait(false);

            var rules = JsonConvert.DeserializeObject<List<MetricEnrichmentRule>>(response, this.serializerSettings);
            return rules;
        }

        /// <summary>
        /// Save the metric configuration provided.
        /// </summary>
        /// <param name="rule">Rule to save.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task SaveAsync(MetricEnrichmentRule rule)
        {
            if (rule == null)
            {
                throw new ArgumentNullException(nameof(rule));
            }

            var validationFailureMessage = rule.Validate();
            if (!string.IsNullOrEmpty(validationFailureMessage))
            {
                throw new ArgumentException(validationFailureMessage);
            }

            if (!rule.MonitoringAccountFilter.Equals("*"))
            {
                throw new ArgumentException("Monitoring account needs to be * as this is stamp level rule.");
            }

            var path = $"{this.configurationUrlPrefix}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(string.Empty))
            {
                Path = path,
                Query = "apiVersion=1"
            };

            var serializedMetric = JsonConvert.SerializeObject(rule, Formatting.Indented, this.serializerSettings);

            await HttpClientHelper.GetResponseAsStringAsync(
                uriBuilder.Uri,
                HttpMethod.Post,
                this.httpClient,
                string.Empty,
                this.configurationUrlPrefix,
                serializedContent: serializedMetric).ConfigureAwait(false);
        }

        /// <summary>
        /// Deletes the enrichment rule.
        /// </summary>
        /// <param name="rule">Rule to be deleted.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task DeleteAsync(MetricEnrichmentRule rule)
        {
            if (rule == null)
            {
                throw new ArgumentNullException(nameof(rule));
            }

            var validationFailureMessage = rule.Validate();
            if (!string.IsNullOrEmpty(validationFailureMessage))
            {
                throw new ArgumentException(validationFailureMessage);
            }

            var path = $"{this.configurationUrlPrefix}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(string.Empty))
            {
                Path = path
            };

            var serializedMetric = JsonConvert.SerializeObject(rule, Formatting.Indented, this.serializerSettings);
            await HttpClientHelper.GetResponseAsStringAsync(
                uriBuilder.Uri,
                HttpMethod.Delete,
                this.httpClient,
                string.Empty,
                this.configurationUrlPrefix,
                serializedContent: serializedMetric).ConfigureAwait(false);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Histogram.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// <author email="selavrin">
//     <NAME>
// </author>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using global::Metrics.Services.Common;

    /// <summary>
    /// Represents a histogram.
    /// </summary>
    public sealed class Histogram : IReadOnlyHistogram, IPoolTrackable
    {
        private readonly List<KeyValuePair<ulong, uint>> histogram = new List<KeyValuePair<ulong, uint>>();
        private uint count;

        /// <inheritdoc />
        PoolObjectTrackingInfo IPoolTrackable.PoolObjectTrackingInfo { get; set; }

        /// <summary>
        /// Gets the number of samples in the histogram.
        /// </summary>
        public int SamplesCount => this.histogram.Count;

        /// <summary>
        /// Gets the list of histogram samples: ordered pairs of value-count.
        /// </summary>
        public IEnumerable<KeyValuePair<ulong, uint>> Samples => this.histogram;

        /// <summary>
        /// Reinitializes histogram object with new data.
        /// </summary>
        /// <param name="histogramData">Unordered pairs of value-count from which histogram will be constructed.</param>
        public void Initialize(IEnumerable<KeyValuePair<ulong, uint>> histogramData)
        {
            this.histogram.Clear();
            this.histogram.AddRange(histogramData);
            this.histogram.Sort((i1, i2) => (int)i1.Key - (int)i2.Key);
            this.count = (uint)this.histogram.Sum(h => h.Value);
        }

        /// <summary>
        /// Calculates percentile from the histogram.
        /// </summary>
        /// <param name="percent">Percent value for which to calculate percentile.</param>
        /// <returns>Percentile value.</returns>
        public float GetPercentile(float percent)
        {
            if (this.count == 0)
            {
                return 0;
            }

            if (percent < 0 || percent > 100)
            {
                throw new ArgumentOutOfRangeException(nameof(percent), "Percent should be within [0;100] range.");
            }

            // Find index of the first value, whose last entry index is higher than index of the percentile for given percent
            float percentileIndex = percent * this.count / 100;
            uint currentIndex = 0;
            int index = -1;
            for (int i = 0; i < this.histogram.Count; ++i)
            {
                currentIndex += this.histogram[i].Value;
                if (percentileIndex <= currentIndex)
                {
                    index = i;
                    break;
                }
            }

            // Calculate percentile value based on found index
            if (index == 0)
            {
                return this.histogram[index].Key;
            }

            // When percentile index lies between two row values in the original sorted array, use weighted average to calculate percentile value (same approach is used in PerfCollector)
            var coefficient = percentileIndex - currentIndex + this.histogram[index].Value;
            return coefficient < 1 ?
                       (this.histogram[index - 1].Key * (1 - coefficient)) + (this.histogram[index].Key * coefficient) : this.histogram[index].Key;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QueryFilter.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    /// <summary>
    /// Operator used to compare with the <see cref="QueryFilter.Operand"/>.
    /// </summary>
    public enum Operator
    {
        /// <summary>
        /// Operator was not specified, query is not considered valid.
        /// </summary>
        Undefined,

        /// <summary>
        /// Operator equal.
        /// </summary>
        Equal,

        /// <summary>
        /// Operator not equal.
        /// </summary>
        NotEqual,

        /// <summary>
        /// Operator greater than.
        /// </summary>
        GreaterThan,

        /// <summary>
        /// Operator less than.
        /// </summary>
        LessThan,

        /// <summary>
        /// Operator less than or equal.
        /// </summary>
        LessThanOrEqual,

        /// <summary>
        /// Operator greater than or equal.
        /// </summary>
        GreaterThanOrEqual
    }

    /// <summary>
    /// Type that represents the filter used in the query.
    /// </summary>
    public sealed class QueryFilter
    {
        /// <summary>
        /// Filter object representing that no filtering should be done.
        /// </summary>
        public static readonly QueryFilter NoFilter = new QueryFilter(Operator.Undefined, 0.0);

        /// <summary>
        /// Initializes a new instance of the <see cref="QueryFilter"/> class.
        /// Create a filter to be used in a filtered dimension query.
        /// </summary>
        /// <param name="operator">The operator.</param>
        /// <param name="operand">The operand.</param>
        public QueryFilter(Operator @operator, double operand)
        {
            this.Operator = @operator;
            this.Operand = operand;
        }

        /// <summary>
        /// Operator to use when comparing time series aggregate to <see cref="Operand"/>.
        /// </summary>
        public Operator Operator { get; private set; }

        /// <summary>
        /// The value to compare to.
        /// </summary>
        public double Operand { get; private set; }

        /// <summary>
        /// Returns a string representing the current values of the instance, helpful for debugging and logging.
        /// </summary>
        /// <returns>
        /// A string representing the current values of the instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0} {1}", this.Operator, this.Operand);
        }

        /// <summary>
        /// Determine if the provided object is equal to this <see cref="QueryFilter"/>.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns>The result of the equality test.</returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as QueryFilter);
        }

        /// <summary>
        /// Get hash code for this object.
        /// </summary>
        /// <returns>The hash code.</returns>
        public override int GetHashCode()
        {
            return this.Operator.GetHashCode() ^ this.Operand.GetHashCode();
        }

        /// <summary>
        /// Compare the provided <see cref="QueryFilter"/> to this one.
        /// </summary>
        /// <param name="otherFilter">The other query filter.</param>
        /// <returns>The result of the equality test.</returns>
        private bool Equals(QueryFilter otherFilter)
        {
            if (otherFilter == null)
            {
                return false;
            }

            return this.Operator.Equals(otherFilter.Operator)
                   && this.Operand == otherFilter.Operand;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ProductionGlobalEnvironmentResolver.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using Microsoft.Cloud.Metrics.Client.Logging;

    using Newtonsoft.Json;

    /// <summary>
    /// A helper class to resolve the production global environment.
    /// </summary>
    /// <remarks>
    /// The LX project consists of two distinct clouds.  Neither will have connectivity to external networks and L3 hosted DNS names cannot be used.
    /// This impacts our current behavior of users not specifying a specific endpoint and us utilizing the fixed L3 hosted global stamp DNS to seed locating the appropriate owning stamp for a given account.
    /// The goal is to allow this behavior to continue while not requiring customers to modify their code and configuration when moving into LX,
    /// and this helper class tries to determine the production global environment by resolving the DNS names of a list of potential global environments and return the first succeeded one.
    /// </remarks>
    public class ProductionGlobalEnvironmentResolver
    {
        private static readonly object LogId = Logger.CreateCustomLogId("ProductionGlobalEnvironmentResolver");
        private static readonly string[] PotentialProductionGlobalEnvironments =
        {
            "global.metrics.nsatc.net",
            "global.metrics.trafficmanager.net",
        };

        private static string globalStampHostName;

        /// <summary>
        /// Gets the global stamp host name.
        /// </summary>
        /// <returns>The global stamp host name.</returns>
        public static string ResolveGlobalStampHostName()
        {
            if (globalStampHostName != null)
            {
                return globalStampHostName;
            }

            for (int i = 0; i < PotentialProductionGlobalEnvironments.Length; i++)
            {
                var resolvedIp = ConnectionInfo.ResolveIp(PotentialProductionGlobalEnvironments[i], throwOnFailure: false).GetAwaiter().GetResult();
                if (resolvedIp != null)
                {
                    globalStampHostName = PotentialProductionGlobalEnvironments[i];
                    return PotentialProductionGlobalEnvironments[i];
                }

                Logger.Log(LoggerLevel.Error, LogId, "ProductionGlobalEnvironmentResolver", $"Failed to resolve {PotentialProductionGlobalEnvironments[i]}.");
            }

            string errorMsg = $"ProductionGlobalEnvironmentResolver - None of the host names can be resolved: {JsonConvert.SerializeObject(PotentialProductionGlobalEnvironments)}.";
            Logger.Log(LoggerLevel.Error, LogId, "ProductionGlobalEnvironmentResolver", errorMsg);

            throw new MetricsClientException(errorMsg);
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="BitBinaryWriterV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.BitHelper
{
    using System;
    using System.IO;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// A class which allows writing bits to the stream.
    /// We accumulate current byte value bit by bit and when it is full, it is written to the stream.
    /// <see cref="Flush" /> method must be called at the end since the last byte to write to stream could be partial.
    /// </summary>
    public sealed class BitBinaryWriterV2
    {
        private readonly BinaryWriter writer;

        /// <summary>
        /// Initializes a new instance of the <see cref="BitBinaryWriterV2"/> class.
        /// </summary>
        /// <param name="writer">The writer.</param>
        public BitBinaryWriterV2(BinaryWriter writer)
        {
            this.writer = writer;
            this.CurrentBitIndex = 0;
            this.CurrentByte = 0;
        }

        /// <summary>
        /// Gets the current bit position in the buffer.
        /// </summary>
        public byte CurrentBitIndex { get; private set; }

        /// <summary>
        /// Gets the value of the currently accumulated byte.
        /// </summary>
        public byte CurrentByte { get; private set; }

        /// <summary>
        /// Gets the underlying binary writer.
        /// </summary>
        public BinaryWriter BinaryWriter => this.writer;

        /// <summary>
        /// Write bits to the stream.
        /// </summary>
        /// <param name="oldValue">The value.</param>
        /// <param name="numBits">The number bits.</param>
        /// <param name="positionOfLeastSignificantBit">The position of least significant bit, starting with 0.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public unsafe void WriteBits(long oldValue, int numBits, int positionOfLeastSignificantBit)
        {
            var l = &oldValue;
            var value = *(ulong*)l;
            int endBit = 64 - positionOfLeastSignificantBit;
            int startBit = 64 - (numBits + positionOfLeastSignificantBit);
            var bitsLeftToCopy = endBit - startBit;
            value <<= startBit;

            while (bitsLeftToCopy > 0)
            {
                var bitsToCopy = Math.Min(bitsLeftToCopy, 8 - this.CurrentBitIndex);
                this.CurrentByte = (byte)(((ulong)this.CurrentByte << (bitsToCopy - 1)) | (value >> (64 - bitsToCopy)));

                value <<= bitsToCopy;
                bitsLeftToCopy -= bitsToCopy;

                this.CurrentBitIndex += (byte)bitsToCopy;
                if (this.CurrentBitIndex == 8)
                {
                    this.writer.Write(this.CurrentByte);
                    this.CurrentByte = 0;
                    this.CurrentBitIndex = 0;
                }
                else
                {
                    this.CurrentByte <<= 1;
                }
            }
        }

        /// <summary>
        /// Write bit to the stream.
        /// </summary>
        /// <param name="bit">Bit value to write, where True means 1 and False means 0.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void WriteBit(bool bit)
        {
            this.CurrentByte = (byte)(this.CurrentByte | (bit ? 1 : 0));
            if (++this.CurrentBitIndex == 8)
            {
                this.writer.Write(this.CurrentByte);
                this.CurrentByte = 0;
                this.CurrentBitIndex = 0;
            }
            else
            {
                this.CurrentByte <<= 1;
            }
        }

        /// <summary>
        /// Writes uint value Base-128 encoded.
        /// </summary>
        /// <param name="value">Value to write.</param>
        public void WriteUInt32AsBase128(uint value)
        {
            this.WriteUInt64AsBase128(value);
        }

        /// <summary>
        /// Writes ulong value Base-128 encoded.
        /// </summary>
        /// <param name="value">Value to write.</param>
        public void WriteUInt64AsBase128(ulong value)
        {
            var t = value;
            do
            {
                var b = (byte)(t & 0x7f);
                t >>= 7;
                if (t > 0)
                {
                    b |= 0x80;
                }

                this.WriteBits(b, 8, 0);
            }
            while (t > 0);
        }

        /// <summary>
        /// Flush the current byte into stream even if it is partial.
        /// </summary>
        public void WriteTillEndOfByteBoundary()
        {
            if (this.CurrentBitIndex != 0)
            {
                var value = this.CurrentByte << (7 - this.CurrentBitIndex);
                this.writer.Write((byte)value);
                this.CurrentBitIndex = 0;
                this.CurrentByte = 0;
                this.CurrentBitIndex = 0;
            }
        }

        /// <summary>
        /// Flush the current byte into stream even if it is partial.
        /// </summary>
        public void Flush()
        {
            if (this.CurrentBitIndex != 0)
            {
                var value = this.CurrentByte << (7 - this.CurrentBitIndex);
                this.writer.Write((byte)value);
            }
        }
    }
}
<file_sep>﻿// <copyright file="SamplingTypesExtensionMethods.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

namespace Microsoft.Online.Metrics.Serialization
{
    using System.Runtime.CompilerServices;

    /// <summary>
    /// The helper class to hold the extension methods for <see cref="SamplingTypes"/> type.
    /// </summary>
    public static class SamplingTypesExtensionMethods
    {
        /// <summary>
        /// Returns true if the samplingTypes instance has value as one of the bits set.
        /// </summary>
        /// <param name="samplingTypes">The sampling types to check.</param>
        /// <param name="value">The value to look for.</param>
        /// <returns>True if value is present in samplingTypes, false otherwise.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool Includes(this SamplingTypes samplingTypes, SamplingTypes value)
        {
            return (samplingTypes & value) == value;
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MonitoringAccountConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Utility;
    using Newtonsoft.Json;

    /// <summary>
    /// This class manages get and save operations on monitoring account configurations.
    /// </summary>
    public sealed class MonitoringAccountConfigurationManager : IMonitoringAccountConfigurationManager
    {
        private readonly ConnectionInfo connectionInfo;
        private readonly HttpClient httpClient;
        private readonly string monitoringAccountUrlPrefix;
        private readonly string tenantUrlPrefix;
        private readonly string operation;
        private readonly JsonSerializerSettings serializerSettings;

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitoringAccountConfigurationManager"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the MDM endpoint being used.</param>
        public MonitoringAccountConfigurationManager(ConnectionInfo connectionInfo)
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            this.connectionInfo = connectionInfo;

            this.operation = this.connectionInfo.GetAuthRelativeUrl("v1/config");

            this.monitoringAccountUrlPrefix = this.operation + "/monitoringAccount/";
            this.tenantUrlPrefix = this.operation + "/tenant/";

            this.httpClient = HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo);

            var migrations = new[]
            {
                new ClientAssemblyMigration(
                    "Metrics.Server",
                    "Microsoft.Online.Metrics.Server.Utilities.ConfigurationUpdateResult",
                    typeof(ConfigurationUpdateResult))
            };

            this.serializerSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = new ClientAssemblyMigrationSerializationBinder(migrations)
            };
        }

        /// <summary>
        /// Get the monitoring account specified by the monitoring account name.
        /// </summary>
        /// <param name="monitoringAccountName">The name of the monitoring account.</param>
        /// <returns>The monitoring account.</returns>
        public async Task<IMonitoringAccount> GetAsync(string monitoringAccountName)
        {
            if (string.IsNullOrWhiteSpace(monitoringAccountName))
            {
                throw new ArgumentException("Monitoring account must not be blank or null.");
            }

            var path = $"{this.monitoringAccountUrlPrefix}/{monitoringAccountName}";

            // Call CheckIfGlobalEndpointWithRetry to avoid exception thrown by unresolved globalEnvrionments.
            if (this.connectionInfo.CheckIfGlobalEndpointWithRetry())
            {
                // If the global endpoint is used, account information can be retrieved from the cache.
                path += "/cache/true";
            }

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccountName))
            {
                Path = path,
                Query = "version=1"
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Get,
                    this.httpClient,
                    monitoringAccountName,
                    this.operation).ConfigureAwait(false);

                var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
                return JsonConvert.DeserializeObject<MonitoringAccount>(
                    response.Item1,
                    settings);
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode.HasValue &&
                    mce.ResponseStatusCode.Value == HttpStatusCode.NotFound)
                {
                    throw new AccountNotFoundException(
                        $"Account [{monitoringAccountName}] not found. TraceId: [{mce.TraceId}]", mce);
                }

                throw;
            }
        }

        /// <summary>
        /// Creates a monitoring account with provided configuration.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration.</param>
        /// <param name="stampHostName">The stamp name such as prod3.metrics.nsatc.net as documented @ https://aka.ms/mdm-endpoints.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task CreateAsync(IMonitoringAccount monitoringAccount, string stampHostName)
        {
            if (string.IsNullOrWhiteSpace(stampHostName))
            {
                throw new ArgumentException("value is null or empty", nameof(stampHostName));
            }

            if (this.connectionInfo.Endpoint != null)
            {
                throw new ArgumentException("'Endpoint' must not be specified in the constructor for ConnectionInfo to create monitoring accounts.");
            }

            // Check if the client has service admin permission in the global stamp; if not, try to create the account in the target stamp directly.
            var globalStampEndpoint = ConnectionInfo.ResolveGlobalEnvironments()[(int)this.connectionInfo.MdmEnvironment];
            bool hasAccountCreationPermissionInGlobalStamp = await this.HasAccountCreationPermission(globalStampEndpoint).ConfigureAwait(false);

            var endpoint = hasAccountCreationPermissionInGlobalStamp ? globalStampEndpoint : $"https://{stampHostName}/";
            var url = $"{endpoint}{this.monitoringAccountUrlPrefix}{monitoringAccount.Name}/stamp/{stampHostName}";

            await this.PostAsync(monitoringAccount, url).ConfigureAwait(false);
        }

        /// <summary>
        /// Create a new monitoring account named <paramref name="newMonitoringAccountName"/> on stamp <paramref name="stampHostName"/> by copying the common settings from <paramref name="monitoringAccountToCopyFrom" />.
        /// </summary>
        /// <param name="newMonitoringAccountName">The new monitoring account name.</param>
        /// <param name="monitoringAccountToCopyFrom">The name of the monitoring account where common settings are copied from.</param>
        /// <param name="stampHostName">The stamp name such as prod3.metrics.nsatc.net as documented @ https://aka.ms/mdm-endpoints.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task CreateAsync(string newMonitoringAccountName, string monitoringAccountToCopyFrom, string stampHostName)
        {
            if (string.IsNullOrWhiteSpace(newMonitoringAccountName))
            {
                throw new ArgumentException("value is null or empty", nameof(newMonitoringAccountName));
            }

            if (string.IsNullOrWhiteSpace(monitoringAccountToCopyFrom))
            {
                throw new ArgumentException("value is null or empty", nameof(monitoringAccountToCopyFrom));
            }

            if (string.IsNullOrWhiteSpace(stampHostName))
            {
                throw new ArgumentException("value is null or empty", nameof(stampHostName));
            }

            if (this.connectionInfo.Endpoint != null)
            {
                throw new ArgumentException("'Endpoint' must not be specified in the constructor for ConnectionInfo to create monitoring accounts.");
            }

            // Check if the client has service admin permission in the global stamp; if not, try to create the account in the target stamp directly.
            var globalStampEndpoint = ConnectionInfo.ResolveGlobalEnvironments()[(int)this.connectionInfo.MdmEnvironment];
            bool hasAccountCreationPermissionInGlobalStamp = await this.HasAccountCreationPermission(globalStampEndpoint).ConfigureAwait(false);

            var endpoint = hasAccountCreationPermissionInGlobalStamp ? globalStampEndpoint : $"https://{stampHostName}/";
            var url = $"{endpoint}{this.monitoringAccountUrlPrefix}{newMonitoringAccountName}/stamp/{stampHostName}/copy/{monitoringAccountToCopyFrom}";

            try
            {
                await HttpClientHelper.GetResponse(
                    new Uri(url),
                    HttpMethod.Post,
                    this.httpClient,
                    newMonitoringAccountName,
                    this.operation).ConfigureAwait(false);
            }
            catch (MetricsClientException mce)
            {
                ThrowSpecificExceptionIfPossible(mce, newMonitoringAccountName);
                throw;
            }
        }

        /// <summary>
        /// Save the monitoring account configuration provided.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration to save.</param>
        /// <param name="skipVersionCheck">Flag indicating whether or not the version flag should be honored.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task SaveAsync(IMonitoringAccount monitoringAccount, bool skipVersionCheck = false)
        {
            var path =
                $"{this.monitoringAccountUrlPrefix}/{monitoringAccount.Name}/skipVersionCheck/{skipVersionCheck}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount.Name))
            {
                Path = path
            };

            var url = uriBuilder.Uri.ToString();

            await this.PostAsync(monitoringAccount, url).ConfigureAwait(false);
        }

        /// <summary>
        /// Delete the monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration to delete.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task DeleteAsync(string monitoringAccount)
        {
            var path = $"{this.tenantUrlPrefix}/{monitoringAccount}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount))
            {
                Path = path
            };

            var url = uriBuilder.Uri;

            await HttpClientHelper.GetResponse(
                url,
                HttpMethod.Delete,
                this.httpClient,
                monitoringAccount,
                this.operation).ConfigureAwait(false);
        }

        /// <summary>
        /// Un-Delete the monitoring account.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration to un-delete.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task UnDeleteAsync(string monitoringAccount)
        {
            var path = $"{this.tenantUrlPrefix}/{monitoringAccount}/undelete";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount))
            {
                Path = path
            };

            var url = uriBuilder.Uri;

            await HttpClientHelper.GetResponse(
                url,
                HttpMethod.Post,
                this.httpClient,
                monitoringAccount,
                this.operation).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<ConfigurationUpdateResult>> SyncMonitoringAccountConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var mirrorOperation = $"{this.monitoringAccountUrlPrefix}replicateConfigurationToMirrorAccounts";

            var path =
                $"{this.monitoringAccountUrlPrefix}{monitoringAccount.Name}/replicateConfigurationToMirrorAccounts/skipVersionCheck/{skipVersionCheck}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetGlobalEndpoint())
            {
                Path = path
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Post,
                    this.httpClient,
                    monitoringAccount.Name,
                    mirrorOperation).ConfigureAwait(false);

                return JsonConvert.DeserializeObject<ConfigurationUpdateResult[]>(
                    response.Item1,
                    this.serializerSettings);
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode == HttpStatusCode.Unauthorized)
                {
                    var exMsg = $"Unable to sync configuration for monitoring account:{monitoringAccount.Name} as "
                        + $"user doesn't have permission to update configurations. Response:{mce.Message}";

                    throw new ConfigurationValidationException(exMsg, ValidationType.ServerSide, mce);
                }

                throw;
            }
        }

        private static void ThrowSpecificExceptionIfPossible(MetricsClientException mce, string monitoringAccountName)
        {
            if (mce.ResponseStatusCode.HasValue)
            {
                switch (mce.ResponseStatusCode.Value)
                {
                    case HttpStatusCode.NotFound:
                        throw new AccountNotFoundException(
                            $"Account [{monitoringAccountName}] not found. TraceId: [{mce.TraceId}]",
                            mce);
                    case HttpStatusCode.BadRequest:
                        throw new ConfigurationValidationException(
                            $"Account [{monitoringAccountName}] could not be saved because validation failed. Response: {mce.Message}",
                            ValidationType.ServerSide,
                            mce);
                }
            }
        }

        /// <summary>
        /// Validates that the monitoring account provided can be sent to the server to be saved.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration being saved.</param>
        private static void Validate(IMonitoringAccount monitoringAccount)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(monitoringAccount.Name))
            {
                throw new ArgumentException("Monitoring account name cannot be null or empty.");
            }

            if (monitoringAccount.Permissions == null || !monitoringAccount.Permissions.Any())
            {
                throw new ArgumentException("One or more permissions must be specified for this account.  These can include users, security groups, or certificates.");
            }
        }

        private async Task<bool> HasAccountCreationPermission(string stampEndpoint)
        {
            var monitoringAccount = "Monitoring account is not relevant here";
            var relativeUrl = this.connectionInfo.GetAuthRelativeUrl($"v1/config/security/writepermissions/tenant/{monitoringAccount}");
            var urlToCheckPermission = $"{stampEndpoint}/{relativeUrl}";

            Tuple<string, HttpResponseMessage> response;
            try
            {
                response = await HttpClientHelper.GetResponse(
                    new Uri(urlToCheckPermission),
                    HttpMethod.Get,
                    this.httpClient,
                    monitoringAccount,
                    this.operation).ConfigureAwait(false);
            }
            catch (MetricsClientException e)
            {
                if (e.ResponseStatusCode == HttpStatusCode.Forbidden)
                {
                    return false;
                }

                throw;
            }

            var permissions = JsonConvert.DeserializeObject<string[]>(response.Item1);
            return permissions != null && permissions.Contains("TenantConfiguration");
        }

        private async Task PostAsync(IMonitoringAccount monitoringAccount, string url)
        {
            Validate(monitoringAccount);
            var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
            var serializedMonitoringAccount = JsonConvert.SerializeObject(
                monitoringAccount,
                Formatting.Indented,
                settings);

            try
            {
                await HttpClientHelper.GetResponse(
                    new Uri(url),
                    HttpMethod.Post,
                    this.httpClient,
                    monitoringAccount.Name,
                    this.operation,
                    serializedContent: serializedMonitoringAccount).ConfigureAwait(false);
            }
            catch (MetricsClientException mce)
            {
                ThrowSpecificExceptionIfPossible(mce, monitoringAccount.Name);
                throw;
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NativeMethods.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Native methods required to interact with ETW.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime.InteropServices;

    // ReSharper disable FieldCanBeMadeReadOnly.Global
    // ReSharper disable MemberCanBePrivate.Global
    // ReSharper disable UnusedMember.Global

    /// <summary>
    /// This type contains the P/Invoke declarations needed to interact with ETW.
    /// </summary>
    [SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification = "Not accessible to any 3rd-party MS or not")]
    internal static class NativeMethods
    {
        /// <summary>
        /// Managed version for ERROR_SUCCESS. Windows error code that represents no error, i.e.: success.
        /// </summary>
        internal const uint ErrorSuccess = 0;

        /// <summary>
        /// Managed version for ERROR_INSUFFICIENT_BUFFER. The data area passed to a system call is too small.
        /// </summary>
        internal const uint ErrorInsufficientBuffer = 122;

        /// <summary>
        /// Managed version for ERROR_ALREADY_EXISTS. Cannot create some object because it already exists.
        /// </summary>
        internal const uint ErrorAlreadyExists = 183;

        /// <summary>
        /// Managed version for ERROR_MORE_DATA. More data is available.
        /// </summary>
        internal const uint ErrorMoreData = 234;

        /// <summary>
        /// Managed version for ERROR_WMI_GUID_NOT_FOUND. The GUID passed was not recognized as valid by a WMI data provider.
        /// </summary>
        internal const uint ErrorWmiGuidNotFound = 4200;

        /// <summary>
        /// Managed version for ERROR_WMI_INSTANCE_NOT_FOUND. The instance name passed was not recognized as valid by a WMI data provider.
        /// </summary>
        internal const uint ErrorWmiInstanceNotFound = 4201;

        /// <summary>
        /// Managed version for INVALID_HANDLE_VALUE. Win32 constant the represents an invalid handle.
        /// </summary>
        internal const ulong InvalidHandleValue = unchecked((ulong)(-1));

        /// <summary>
        /// Managed version for INVALID_TRACEHANDLE_64. Represents an invalid trace handle for 64bit apps.
        /// </summary>
        internal const ulong InvalidTracehandle64 = unchecked((ulong)(-1));

        /// <summary>
        /// Managed version for INVALID_TRACEHANDLE_32. Represents an invalid trace handle for 32bit apps.
        /// </summary>
        internal const ulong InvalidTracehandle32 = 0x00000000FFFFFFFF;

        /// <summary>
        /// Managed version for PROCESS_TRACE_MODE_REAL_TIME. Indicates to ETW to process the trace in
        /// real-time (live) mode.
        /// </summary>
        internal const uint ProcessTraceModeRealTime = 0x00000100;

        /// <summary>
        /// Managed version for PROCESS_TRACE_MODE_RAW_TIMESTAMP. Indicates to ETW to return the event
        /// timestamps with their raw value instead of transforming them to FILETIME.
        /// </summary>
        internal const uint ProcessTraceModeRawTimestamp = 0x00001000;

        /// <summary>
        /// Managed version for PROCESS_TRACE_MODE_EVENT_RECORD. Indicates to ETW to callback for each
        /// event using the modern (Crimson) event format.
        /// </summary>
        internal const uint ProcessTraceModeEventRecord = 0x10000000;

        /// <summary>
        /// Managed version for EVENT_TRACE_REAL_TIME_MODE. Indicates to ETW to process the trace in real
        /// time (live) mode.
        /// </summary>
        internal const uint EventTraceRealTimeMode = 0x00000100;

        /// <summary>
        /// Managed version for EVENT_TRACE_FILE_MODE_SEQUENTIAL. Indicates to ETW that the ETW events
        /// should be written sequentially to a file.
        /// </summary>
        internal const uint EventTraceFileModeSequential = 0x00000001;

        /// <summary>
        /// Managed version for EVENT_TRACE_PRIVATE_LOGGER_MODE. Indicates to ETW that the session should
        /// be in the private logger mode.
        /// </summary>
        internal const uint EventTracePrivateLoggerMode = 0x00000800;

        /// <summary>
        /// Managed version for EVENT_TRACE_INDEPENDENT_SESSION_MODE. Indicates that a logging session should
        /// not be affected by EventWrite failures in other sessions. Without this flag, if an event cannot be
        /// published to one of the sessions that a provider is enabled to, the event will not get published
        /// to any of the sessions. When this flag is set, a failure to write an event to one session will not
        /// cause the EventWrite function to return an error code in other sessions.
        /// </summary>
        /// <remarks>
        /// Per e-mail thread with ETW owners there is no adverse impact (memory, latency, etc).
        /// </remarks>
        internal const uint EventTraceIndependentSessionMode = 0x08000000;

        /// <summary>
        /// Managed version for EVENT_TRACE_CONTROL_QUERY. Control code used by ETW to query the
        /// properties of a tracing session.
        /// </summary>
        internal const uint EventTraceControlQuery = 0;

        /// <summary>
        /// Managed version for EVENT_TRACE_CONTROL_STOP. Control code used by ETW to stop a tracing
        /// session.
        /// </summary>
        internal const uint EventTraceControlStop = 1;

        /// <summary>
        /// Managed version for EVENT_TRACE_CONTROL_UPDATE. Control code used by ETW to update the
        /// properties of a tracing session.
        /// </summary>
        internal const uint EventTraceControlUpdate = 2;

        /// <summary>
        /// Managed version for EVENT_CONTROL_CODE_ENABLE_PROVIDER. Control code used by ETW to
        /// enable providers.
        /// </summary>
        internal const uint EventControlCodeEnableProvider = 1;

        /// <summary>
        /// Managed version for WNODE_FLAG_TRACED_GUID. Value used to indicate the the structure
        /// contains event tracing information.
        /// </summary>
        internal const uint WnodeFlagTracedGuid = 0x00020000;

        /// <summary>
        /// Managed delegate that represents the BufferCallback Win32 callback function.
        /// </summary>
        /// <param name="eventTraceLog">
        /// It is really an EventTraceLogFile type with information about the buffer, however it is more efficient to marshal
        /// it manually.
        /// </param>
        /// <returns>
        /// True if the processing of the trace should continue, false to terminate the processing.
        /// </returns>
        public delegate bool EventTraceBufferCallback(
            [In] IntPtr eventTraceLog);

        /// <summary>
        /// Managed delegate that represents the EventRecordCallback Win32 callback function.
        /// </summary>
        /// <param name="rawData">
        /// Pointer to a EventRecord instance with the event record.
        /// </param>
        public unsafe delegate void EventRecordCallback(
            [In] EventRecord* rawData);

        /// <summary>
        /// Clock resolution to use when logging the time stamp for each event.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "Original type used in native WinAPI is uint")]
        public enum EtwSessionClockType : uint
        {
            /// <summary>
            /// The default clock type to be used by the session, it is equivalent of selecting PerformanceCounter value.
            /// </summary>
            Default = 0,

            /// <summary>
            /// Indicates that the session uses the OS performance counter, a.k.a.: QPC. The resolution is typically 1000 times
            /// less than the CPU frequency of the box. It is the recommended way to collect high-resolution timestamps in Windows.
            /// </summary>
            PerformanceCounter = 1,

            /// <summary>
            /// Indicates that the session uses the SystemTime clock (with actual resolution of ~15 milliseconds it is actually
            /// the cheaper timestamp available to ETW, the downside is the lack of resolution).
            /// </summary>
            SystemTime = 2,

            /// <summary>
            /// Indicates that the session uses the CPU timestamp (RDTSC instruction to retrieve the TSC). It is the cheapest of
            /// all with the higher resolution but not guaranteed to be in sync between different processors in the box.
            /// </summary>
            CpuTimestamp = 3,
        }

        /// <summary>
        /// Control codes to be used with the <c>ControlTrace</c> Windows API.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "Original type used in native WinAPI is uint")]
        public enum TraceControl : uint
        {
            /// <summary>
            /// Managed version for EVENT_TRACE_CONTROL_QUERY. Control code used by ETW to query the
            /// properties of a tracing session.
            /// </summary>
            Query = 0,

            /// <summary>
            /// Managed version for EVENT_TRACE_CONTROL_STOP. Control code used by ETW to stop a tracing
            /// session.
            /// </summary>
            Stop = 1,

            /// <summary>
            /// Managed version for EVENT_TRACE_CONTROL_UPDATE. Control code used by ETW to update the
            /// properties of a tracing session.
            /// </summary>
            Update = 2,

            /// <summary>
            /// Managed version for EVENT_TRACE_CONTROL_FLUSH. Control code used by ETW to update the
            /// properties of a tracing session.
            /// </summary>
            Flush = 3,
        }

        /// <summary>
        /// Mirrors the native TRACE_QUERY_INFO_CLASS enumerations.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364147(v=vs.85).aspx"/>
        internal enum TraceQueryInfoClass
        {
            /// <summary>
            /// Query an array of GUIDs of the providers that are registered on the computer.
            /// </summary>
            TraceGuidQueryList,

            /// <summary>
            /// Query information that each session used to enable the provider.
            /// </summary>
            TraceGuidQueryInfo,

            /// <summary>
            /// Query an array of GUIDs of the providers that registered themselves in the same
            /// process as the calling process.
            /// </summary>
            TraceGuidQueryProcess,

            /// <summary>
            /// Query the setting for call stack tracing for kernel events.
            /// The value is supported on Windows 7, Windows Server 2008 R2, and later.
            /// </summary>
            TraceStackTracingInfo,

            /// <summary>
            /// Query the setting for the EnableFlags for the system trace provider. For more information,
            /// see the EVENT_TRACE_PROPERTIES structure.
            /// The value is supported on Windows 8, Windows Server 2012, and later.
            /// </summary>
            TraceSystemTraceEnableFlagsInfo,

            /// <summary>
            /// Queries the setting for the sampling profile interval for the supplied source.
            /// The value is supported on Windows 8, Windows Server 2012, and later.
            /// </summary>
            TraceSampledProfileIntervalInfo,

            /// <summary>
            /// Query which sources will be traced.
            /// The value is supported on Windows 8, Windows Server 2012, and later.
            /// </summary>
            TraceProfileSourceConfigInfo,

            /// <summary>
            /// Query the setting for sampled profile list information.
            /// The value is supported on Windows 8, Windows Server 2012, and later.
            /// </summary>
            TraceProfileSourceListInfo,

            /// <summary>
            /// Query the list of system events on which performance monitoring counters will be collected.
            /// The value is supported on Windows 8, Windows Server 2012, and later.
            /// </summary>
            TracePmcEventListInfo,

            /// <summary>
            /// Query the list of performance monitoring counters to collect.
            /// The value is supported on Windows 8, Windows Server 2012, and later.
            /// </summary>
            TracePmcCounterListInfo,

            /// <summary>
            /// Marks the last value in the enumeration. Do not use.
            /// </summary>
            MaxTraceSetInfoClass
        }

        /// <summary>
        /// P/Invoke declaration for <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364117(v=vs.85).aspx">
        /// StartTrace</see>.
        /// </summary>
        /// <param name="sessionHandle">
        /// Handle to the event tracing session.
        /// </param>
        /// <param name="sessionName">
        /// Name of the session.
        /// </param>
        /// <param name="properties">
        /// Properties of the session.
        /// </param>
        /// <returns>
        /// The Win32 error code of the call (zero indicates success, i.e. ERROR_SUCCESS Win32 error code).
        /// </returns>
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern int StartTrace(
            [Out] out ulong sessionHandle,
            [In] string sessionName,
            [In][Out] IntPtr properties);

        /// <summary>
        /// P/Invoke declaration for <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363696(v=vs.85).aspx">
        /// ControlTrace</see>.
        /// </summary>
        /// <param name="sessionHandle">
        /// Handle of the event tracing session.
        /// </param>
        /// <param name="sessionName">
        /// Name of the event tracing session.
        /// </param>
        /// <param name="properties">
        /// Properties of the session.
        /// </param>
        /// <param name="controlCode">
        /// Control code being passed to the session.
        /// </param>
        /// <returns>
        /// The Win32 error code of the call (zero indicates success, i.e. ERROR_SUCCESS Win32 error code).
        /// </returns>
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern int ControlTrace(
            [In] ulong sessionHandle,
            [In] string sessionName,
            [In][Out] IntPtr properties,
            [In] uint controlCode);

        /// <summary>
        /// P/Invoke declaration for <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd392305(v=vs.85).aspx">
        /// EnableTraceEx2</see>.
        /// </summary>
        /// <param name="traceHandle">
        /// Handle to the trace session to which the provider is going to be enabled or disabled.
        /// </param>
        /// <param name="providerGuid">
        /// Id of the provider to be enabled or disabled.
        /// </param>
        /// <param name="controlCode">
        /// Control code to be passed to the provider.
        /// </param>
        /// <param name="level">
        /// Importance level for which the provider is going to be set (only events with this severity and higher will be
        /// collected).
        /// </param>
        /// <param name="matchAnyKeyword">
        /// Events that will be collected need to match at least one of the bits in the keyword.
        /// </param>
        /// <param name="matchAllKeyword">
        /// Only events matching all the bits in the keyword will be collected.
        /// </param>
        /// <param name="timeoutMilliseconds">
        /// Timeout in milliseconds for the call to the method.
        /// </param>
        /// <param name="enableParameters">
        /// Parameters used to enable the provider<see href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd392306(v=vs.85).aspx"/>.
        /// </param>
        /// <returns>
        /// The Win32 error code of the call (zero indicates success, i.e. ERROR_SUCCESS Win32 error code).
        /// </returns>
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern int EnableTraceEx2(
            [In] ulong traceHandle,
            [In] ref Guid providerGuid,
            [In] uint controlCode,
            [In] byte level,
            [In] ulong matchAnyKeyword,
            [In] ulong matchAllKeyword,
            [In] uint timeoutMilliseconds,
            [In][Optional] IntPtr enableParameters);

        /// <summary>
        /// P/Invoke declaration for ZeroMemory Win32 function.
        /// </summary>
        /// <param name="handle">
        /// Handle to the memory to be zeroed.
        /// </param>
        /// <param name="length">
        /// Number of bytes that should be zeroed.
        /// </param>
        [DllImport("kernel32.dll", SetLastError = true)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern void ZeroMemory(IntPtr handle, uint length);

        /// <summary>
        /// P/Invoke declaration for the <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364089(v=vs.85).aspx">
        /// OpenTrace</see> function.
        /// </summary>
        /// <param name="traceLog">
        /// Type with the information about the trace to be opened.
        /// </param>
        /// <returns>
        /// If successful it returns a handle to the trace, otherwise a INVALID_PROCESSTRACE_HANDLE (note that this handle is
        /// different if the process is running as a Windows on Windows).
        /// </returns>
        [DllImport(
            "advapi32.dll",
            EntryPoint = "OpenTraceW",
            CharSet = CharSet.Unicode,
            SetLastError = true)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern ulong OpenTrace(
            [In][Out] ref EventTraceLogfilew traceLog);

        /// <summary>
        /// P/Invoke declaration for the <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364093(v=vs.85).aspx">
        /// ProcessTrace</see> function.
        /// </summary>
        /// <param name="handleArray">
        /// Array to with the handles of all traces to be processed.
        /// </param>
        /// <param name="handleCount">
        /// Counter of the handles in the array.
        /// </param>
        /// <param name="startTime">
        /// The start time for which one wants to receive events from the traces.
        /// </param>
        /// <param name="endTime">
        /// The end time for which one wants to stop receiving events from the traces.
        /// </param>
        /// <returns>
        /// It returns 0 (ERROR_SUCCESS) in case of success and Win32 system error code in case of error.
        /// </returns>
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern int ProcessTrace(
            [In] ulong[] handleArray,
            [In] uint handleCount,
            [In] IntPtr startTime,
            [In] IntPtr endTime);

        /// <summary>
        /// P/Invoke declaration for<see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363686(v=vs.85).aspx">CloseTrace</see> function.
        /// </summary>
        /// <param name="traceHandle">
        /// The trace handle to be closed.
        /// </param>
        /// <returns>
        /// It returns 0 (ERROR_SUCCESS) in case of success and Win32 system error code in case of error.
        /// </returns>
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern int CloseTrace(
            [In] ulong traceHandle);

        /// <summary>
        /// P/Invoke declaration for EnumerateTraceGuidsEx.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363714(v=vs.85).aspx"/>
        /// <param name="traceQueryInfoClass">
        /// Determines the type of information to include with the list of registered providers.
        /// For possible values, see the TRACE_QUERY_INFO_CLASS enumeration.
        /// </param>
        /// <param name="inBuffer">
        /// GUID of the provider whose information you want to retrieve. Specify the GUID only
        /// if TraceQueryInfoClass is TraceGuidQueryInfo.
        /// </param>
        /// <param name="inBufferSize">
        /// Size, in bytes, of the data InBuffer.
        /// </param>
        /// <param name="outBuffer">
        /// Application-allocated buffer that contains the enumerated information. The format of
        /// the information depends on the value of TraceQueryInfoClass. For details, see Remarks.
        /// </param>
        /// <param name="outBufferSize">
        /// Size, in bytes, of the OutBuffer buffer. If the function succeeds, the ReturnLength
        /// parameter receives the size of the buffer used. If the buffer is too small, the function
        /// returns ERROR_INSUFFICIENT_BUFFER and the ReturnLength parameter receives the required
        /// buffer size. If the buffer size is zero on input, no data is returned in the buffer and
        /// the ReturnLength parameter receives the required buffer size.
        /// </param>
        /// <param name="returnLength">
        /// Actual size of the data in OutBuffer, in bytes.
        /// </param>
        /// <returns>
        /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails,
        /// the return value is one of the system error codes.
        /// </returns>
        [DllImport("advapi32.dll")]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern unsafe int EnumerateTraceGuidsEx(
            TraceQueryInfoClass traceQueryInfoClass,
            void* inBuffer,
            int inBufferSize,
            void* outBuffer,
            int outBufferSize,
            ref int returnLength);

        /// <summary>
        /// P/Invoke declaration for QueryAllTraces.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364102(v=vs.85).aspx"/>
        /// <param name="propertyArray">
        /// An array of pointers to EVENT_TRACE_PROPERTIES structures that receive session properties and
        /// statistics for the event tracing sessions.
        /// </param>
        /// <param name="propertyArrayCount">
        /// Number of structures in the PropertyArray array.
        /// </param>
        /// <param name="sessionCount">
        /// Actual number of event tracing sessions started on the computer.
        /// </param>
        /// <returns>
        /// If the function succeeds, the return value is ERROR_SUCCESS. If the function fails,
        /// the return value is one of the system error codes.
        /// </returns>
        [DllImport("advapi32.dll")]
        [SuppressMessage("Microsoft.Security", "CA5122:PInvokesShouldNotBeSafeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        internal static extern unsafe int QueryAllTracesW(
            [In][Out] void* propertyArray,
            [In] uint propertyArrayCount,
            [In][Out] ref uint sessionCount);

        /// <summary>
        /// Managed version if WNODE_HEADER.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct WnodeHeader
        {
            /// <summary>
            /// Gets the total size of memory allocated, in bytes, for the event tracing
            /// model Properties. The size of memory must include the room for the
            /// EVENT_TRACE_PROPERTIES structure plus the model name string and log file
            /// name string that follow the structure in memory.
            /// </summary>
            public uint BufferSize;

            /// <summary>
            /// Gets a value reserved for internal use.
            /// </summary>
            public uint ProviderId;

            /// <summary>
            /// Gets, on output, the handle to the event tracing model.
            /// </summary>
            public ulong HistoricalContext;

            /// <summary>
            /// Gets the time at which the information in this structure was updated,
            /// in 100-nanosecond intervals since midnight, January 1, 1601.
            /// </summary>
            public ulong TimeStamp;

            /// <summary>
            /// Gets the GUID of the model.
            /// </summary>
            public Guid Guid;

            /// <summary>
            /// Gets the clock resolution to use when logging the time stamp for each
            /// event. The default is query performance counter (QPC).
            /// </summary>
            public EtwSessionClockType ClientContext;

            /// <summary>
            /// Gets the flags of the model.
            /// </summary>
            /// <remarks>
            /// Must contain WNODE_FLAG_TRACED_GUID to indicate that the structure
            /// contains event tracing information.
            /// </remarks>
            public uint Flags;
        }

        /// <summary>
        /// Managed version of EVENT_TRACE_PROPERTIES. Note that it cannot be used directly with the P/Invoke functions because
        /// extra information is added to the end of the struct (see LogFileNameOffset and LoggerNameOffset).
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct EventTraceProperties
        {
            /// <summary>
            /// Gets the WNODE_HEADER structure associated to the trace.
            /// </summary>
            public WnodeHeader Wnode;

            /// <summary>
            /// Gets the amount of memory allocated for each event tracing model
            /// buffer, in kilobytes.
            /// </summary>
            public uint BufferSize;

            /// <summary>
            /// Gets the minimum number of buffers allocated for the event tracing
            /// model's buffer pool.
            /// </summary>
            public uint MinimumBuffers;

            /// <summary>
            /// Gets the maximum number of buffers allocated for the event tracing
            /// model's buffer pool.
            /// </summary>
            public uint MaximumBuffers;

            /// <summary>
            /// Gets the maximum size of the file used to log events, in megabytes.
            /// </summary>
            public uint MaximumFileSize;

            /// <summary>
            /// Gets the logging file mode for the trace.
            /// </summary>
            public uint LogFileMode;

            /// <summary>
            /// Gets the time to wait before flushing buffers, in seconds. If zero,
            /// ETW flushes buffers as soon as they become full. If non-zero, ETW
            /// flushes all buffers that contain events based on the timer value.
            /// </summary>
            public uint FlushTimer;

            /// <summary>
            /// Gets, via bit flags, which events are enabled for a kernel logger model.
            /// </summary>
            public uint EnableFlags;

            /// <summary>
            /// Gets a value that is not used by ETW.
            /// </summary>
            public int AgeLimit;

            /// <summary>
            /// Gets, on output, the number of buffers allocated for the event tracing
            /// model's buffer pool.
            /// </summary>
            public uint NumberOfBuffers;

            /// <summary>
            /// Gets, on output, the number of buffers that are allocated but unused
            /// in the event tracing model's buffer pool.
            /// </summary>
            public uint FreeBuffers;

            /// <summary>
            /// Gets, on output, the number of events that were not recorded.
            /// </summary>
            public uint EventsLost;

            /// <summary>
            /// Gets, on output, the number of buffers written.
            /// </summary>
            public uint BuffersWritten;

            /// <summary>
            /// Gets, on output, the number of buffers that could not be written
            /// to the log file.
            /// </summary>
            public uint LogBuffersLost;

            /// <summary>
            /// Gets, on output, the number of buffers that could not be delivered
            /// in real-time to the consumer.
            /// </summary>
            public uint RealTimeBuffersLost;

            /// <summary>
            /// Gets, on output, the thread identifier for the event tracing model.
            /// </summary>
            [SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification = "Not accessible to any 3rd-party MS or not")]
            public IntPtr LoggerThreadId;

            /// <summary>
            /// Gets the offset from the start of the structure's allocated memory to
            /// beginning of the null-terminated string that contains the log file name.
            /// </summary>
            public uint LogFileNameOffset;

            /// <summary>
            /// Gets the offset from the start of the structure's allocated memory to
            /// beginning of the null-terminated string that contains the model name.
            /// </summary>
            public uint LoggerNameOffset;
        }

        /// <summary>
        /// Managed version of ETW_BUFFER_CONTEXT, contains some context information about the ETW buffer in which
        /// the event was collected.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        [SuppressMessage("Microsoft.Security", "CA2151:FieldsWithCriticalTypesShouldBeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        public struct EtwBufferContext
        {
            /// <summary>
            /// Gets the number of the CPU on which the provider process was running.
            /// The number is zero on a single processor computer.
            /// </summary>
            public byte ProcessorNumber;

            /// <summary>
            /// Gets alignment between events (always eight).
            /// </summary>
            public byte Alignment;

            /// <summary>
            /// Gets Identifier of the model that logged the event.
            /// </summary>
            public ushort LoggerId;
        }

        /// <summary>
        /// Simplified managed version of EVENT_HEADER.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        [SuppressMessage("Microsoft.Security", "CA2151:FieldsWithCriticalTypesShouldBeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        public struct EventHeader
        {
            /// <summary>
            /// Gets the size of the event record, in bytes.
            /// </summary>
            public ushort Size;

            /// <summary>
            /// Gets the header eventType (reserved).
            /// </summary>
            public ushort HeaderType;

            /// <summary>
            /// Gets the flags that provide information about the event such as the eventType
            /// of model it was logged to and if the event contains extended data.
            /// </summary>
            public ushort Flags;            // offset: 0x4

            /// <summary>
            /// Gets the eventType of source to use for parsing the event data.
            /// </summary>
            public ushort EventProperty;

            /// <summary>
            /// Gets the thread that generated the event.
            /// </summary>
            public int ThreadId;            // offset: 0x8

            /// <summary>
            /// Gets the process that generated the event.
            /// </summary>
            public int ProcessId;           // offset: 0xc

            /// <summary>
            /// Gets the  time the event occurred. The resolution depends on the value
            /// of the <see href="http://msdn.microsoft.com/en-us/library/aa364160(v=vs.85).aspx">ClientContext</see> of
            /// the WNODE_HEADER member of the EVENT_TRACE_PROPERTIES structure when the controller created the session.
            /// </summary>
            public long TimeStamp;          // offset: 0x10

            /// <summary>
            /// Gets the GUID that uniquely identifies the provider that logged the event.
            /// </summary>
            public Guid ProviderId;         // offset: 0x18

            /// <summary>
            /// Gets the Id of the event.
            /// </summary>
            public ushort Id;               // offset: 0x28

            /// <summary>
            /// Gets the version of the event.
            /// </summary>
            public byte Version;            // offset: 0x2a

            /// <summary>
            /// Gets the channel of the event.
            /// </summary>
            public byte Channel;

            /// <summary>
            /// Gets the level of the event.
            /// </summary>
            public byte Level;              // offset: 0x2c

            /// <summary>
            /// Gets the opcode of the event.
            /// </summary>
            [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")]
            public byte Opcode;

            /// <summary>
            /// Gets the task of the event.
            /// </summary>
            public ushort Task;

            /// <summary>
            /// Gets the keyword of the event.
            /// </summary>
            public ulong Keyword;

            /// <summary>
            /// Gets the elapsed execution time for kernel-mode instructions, in CPU time units.
            /// </summary>
            public int KernelTime;         // offset: 0x38

            /// <summary>
            /// Gets the elapsed execution time for user-mode instructions, in CPU time units.
            /// </summary>
            public int UserTime;           // offset: 0x3C

            /// <summary>
            /// Gets an identifier that relates two events. For details, see EventWriteTransfer.
            /// </summary>
            public Guid ActivityId;
        }

        /// <summary>
        /// Managed the version of EVENT_RECORD, represents a single modern (a.k.a.: Crimson) ETW event.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        [SuppressMessage("Microsoft.Security", "CA2151:FieldsWithCriticalTypesShouldBeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        public struct EventRecord
        {
            /// <summary>
            /// Gets the header information about the event such as the time stamp for when
            /// it was written. For details, see EVENT_HEADER.
            /// </summary>
            public EventHeader EventHeader;    // size: 80

            /// <summary>
            /// Gets the context information such as the model that logged the event.
            /// For details, see ETW_BUFFER_CONTEXT.
            /// </summary>
            public EtwBufferContext BufferContext;    // size: 4

            /// <summary>
            /// Gets the number of extended data structures in ExtendedData.
            /// </summary>
            public ushort ExtendedDataCount;

            /// <summary>
            /// Gets the Size, in bytes, of the data in UserData.
            /// </summary>
            public ushort UserDataLength;       // offset: 86

            /// <summary>
            /// Gets the extended data items that ETW collects if the controller sets the EnableProperty
            /// parameter of EnableTraceEx. For details, see EVENT_HEADER_EXTENDED_DATA_ITEM.
            /// </summary>
            [SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification = "Not accessible to any 3rd-party MS or not")]
            public IntPtr ExtendedData;

            /// <summary>
            /// Gets the event specific data. To parse this data, see Retrieving Event Data Using TDH.
            /// If the Flags member of EVENT_HEADER is EVENT_HEADER_FLAG_STRING_ONLY, the data is a
            /// null-terminated Unicode string that you do not need TDH to parse.
            /// </summary>
            [SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification = "Not accessible to any 3rd-party MS or not")]
            public IntPtr UserData;

            /// <summary>
            /// Gets the context specified in the Context member of the EVENT_TRACE_LOGFILE structure
            /// that is passed to OpenTrace.
            /// </summary>
            [SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible", Justification = "Not accessible to any 3rd-party MS or not")]
            public IntPtr UserContext;
        }

        /// <summary>
        /// Managed declaration for the native TRACE_ENABLE_INFO structure.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364141(v=vs.85).aspx"/>
        [StructLayout(LayoutKind.Sequential)]
        public struct TraceEnableInfo
        {
            /// <summary>
            /// Indicates if the provider is enabled to the session. The value is TRUE if the provider
            /// is enabled to the session, otherwise, the value is FALSE. This value should always be TRUE.
            /// </summary>
            public uint IsEnabled;

            /// <summary>
            /// Level of detail that the session asked the provider to include in the events. For details,
            /// see the Level parameter of the EnableTraceEx function.
            /// </summary>
            public byte Level;

            /// <summary>
            /// Reserved, do not use.
            /// </summary>
            public byte Reserved1;

            /// <summary>
            /// Identifies the session that enabled the provider.
            /// </summary>
            public ushort LoggerId;

            /// <summary>
            /// Additional information that the session wants ETW to include in the log file. For details,
            /// see the EnableProperty parameter of the EnableTraceEx function.
            /// </summary>
            public uint EnableProperty;

            /// <summary>
            /// Reserved, do not use.
            /// </summary>
            public uint Reserved2;

            /// <summary>
            /// Keywords specify which events the session wants the provider to write. For details, see the
            /// MatchAnyKeyword parameter of the EnableTraceEx function.
            /// </summary>
            public long MatchAnyKeyword;

            /// <summary>
            /// Keywords specify which events the session wants the provider to write. For details, see the
            /// MatchAllKeyword parameter of the EnableTraceEx function.
            /// </summary>
            public long MatchAllKeyword;
        }

        /// <summary>
        /// Managed version of TIME_ZONE_INFORMATION. Used as one field of TRACE_EVENT_LOGFILE, below.
        /// Total struct size is 0xac.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Size = 0xac, CharSet = CharSet.Unicode)]
        internal struct TimeZoneInformation
        {
            /// <summary>
            /// Gets the current bias for local time translation on this computer, in
            /// minutes. The bias is the difference, in minutes, between Coordinated
            /// Universal Time (UTC) and local time.
            /// </summary>
            internal uint bias;

            /// <summary>
            /// Gets the description for standard time. For example, "EST" could
            /// indicate Eastern Standard Time.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            internal string standardName;

            /// <summary>
            /// Gets the A SYSTEMTIME structure that contains a date and local time
            /// when the transition from daylight saving time to standard time occurs
            /// on this operating system.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 8)]
            internal ushort[] standardDate;

            /// <summary>
            /// Gets the bias value to be used during local time translations that
            /// occur during standard time. This member is ignored if a value for
            /// the StandardDate member is not supplied.
            /// </summary>
            internal uint standardBias;

            /// <summary>
            /// Gets a description for daylight saving time. For example, "PDT" could
            /// indicate Pacific Daylight Time.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            internal string daylightName;

            /// <summary>
            /// Gets a SYSTEMTIME structure that contains a date and local time when
            /// the transition from standard time to daylight saving time occurs on
            /// this operating system.
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U2, SizeConst = 8)]
            internal ushort[] daylightDate;

            /// <summary>
            /// Gets the bias value to be used during local time translations that
            /// occur during daylight saving time. This member is ignored if a value
            /// for the DaylightDate member is not supplied.
            /// </summary>
            internal uint daylightBias;
        }

        /// <summary>
        /// Managed version of TRACE_LOGFILE_HEADER is used to define EVENT_TRACE_LOGFILEW.
        /// Total struct size is 0x110.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct TraceLogfileHeader
        {
            /// <summary>
            /// Gets the size of the event tracing model's buffers, in kilobytes.
            /// </summary>
            internal uint BufferSize;

            /// <summary>
            /// Gets the version number of the operating system. This is a roll-up of
            /// the members of VersionDetail. Starting with the low-order bytes, the
            /// first two bytes contain MajorVersion, the next two bytes contain MinorVersion,
            /// the next two bytes contain SubVersion, and the last two bytes contain SubMinorVersion.
            /// </summary>
            internal uint Version;

            /// <summary>
            /// Gets the build number of the operating system.
            /// </summary>
            internal uint ProviderVersion;

            /// <summary>
            /// Gets the number of processors on the system.
            /// </summary>
            internal uint NumberOfProcessors;

            /// <summary>
            /// Gets the time at which the event tracing model stopped, in 100-nanosecond
            /// intervals since midnight, January 1, 1601. This value may be 0 if you are
            /// consuming events in real time or from a log file to which the provide is
            /// still logging events.
            /// </summary>
            internal long EndTime;            // 0x10

            /// <summary>
            /// Gets the time at which the event tracing model stopped, in 100-nanosecond
            /// intervals since midnight, January 1, 1601. This value may be 0 if you are
            /// consuming events in real time or from a log file to which the provide is
            /// still logging events.
            /// </summary>
            internal uint TimerResolution;

            /// <summary>
            /// Gets the maximum size of the log file, in megabytes.
            /// </summary>
            internal uint MaximumFileSize;

            /// <summary>
            /// Gets the current logging mode for the event tracing model.
            /// </summary>
            internal uint LogFileMode;

            /// <summary>
            /// Gets the total number of buffers written by the event tracing model.
            /// </summary>
            internal uint BuffersWritten;

            /// <summary>
            /// Gets a reserved value.
            /// </summary>
            internal uint StartBuffers;

            /// <summary>
            /// Gets the size of a pointer data eventType, in bytes.
            /// </summary>
            internal uint PointerSize;

            /// <summary>
            /// Gets the number of events lost during the event tracing model. Events
            /// may be lost due to insufficient memory or a very high rate of incoming
            /// events.
            /// </summary>
            internal uint EventsLost;         // 0x30

            /// <summary>
            /// Gets the CPU speed, in MHz.
            /// </summary>
            internal uint CpuSpeedInMHz;

            /// <summary>
            /// Gets a value that is not used (present only to keep the struct layout).
            /// </summary>
            internal IntPtr LoggerName; // string, but not CoTaskMemAlloc'd

            /// <summary>
            /// Gets a value that is not used (present only to keep the struct layout).
            /// </summary>
            internal IntPtr LogFileName; // string, but not CoTaskMemAlloc'd

            /// <summary>
            /// Gets A TIME_ZONE_INFORMATION structure that contains the time zone
            /// for the BootTime, EndTime and StartTime members.
            /// </summary>
            internal TimeZoneInformation TimeZone;   // 0x40         0xac size

            /// <summary>
            /// Gets the time at which the system was started, in 100-nanosecond intervals
            /// since midnight, January 1, 1601. BootTime is supported only for traces
            /// written to the Global Logger model.
            /// </summary>
            internal long BootTime;

            /// <summary>
            /// Gets the frequency of the high-resolution performance counter, if one exists.
            /// </summary>
            internal long PerfFreq;

            /// <summary>
            /// Gets the time at which the event tracing model started, in 100-nanosecond
            /// intervals since midnight, January 1, 1601.
            /// </summary>
            internal long StartTime;

            /// <summary>
            /// Gets the the clock eventType. For details, see the ClientContext member of WNODE_HEADER.
            /// </summary>
            internal uint ReservedFlags;

            /// <summary>
            /// Gets the total number of buffers lost during the event tracing model.
            /// </summary>
            internal uint BuffersLost;
        }

        /// <summary>
        /// Managed version of EVENT_TRACE_HEADER, represents the common header of all ETW events.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct EventTraceHeader
        {
            /// <summary>
            /// Gets the total number of bytes of the event. Size includes the size
            /// of the header structure, plus the size of any event-specific data
            /// appended to the header.
            /// </summary>
            internal ushort Size;

            /// <summary>
            /// Gets a reserved value.
            /// </summary>
            internal ushort FieldTypeFlags; // holds our MarkerFlags too

            /// <summary>
            /// Gets the eventType of event. A provider can define their own event types
            /// or use the predefined event types.
            /// </summary>
            internal byte Type;

            /// <summary>
            /// Gets the provider-defined value that defines the severity level used
            /// to generate the event. The value ranges from 0 to 255.
            /// </summary>
            internal byte Level;

            /// <summary>
            /// Gets the version of the event trace class that you are using to log
            /// the event. Specify zero if there is only one version of your event
            /// trace class. The version tells the consumer which MOF class to use
            /// to decipher the event data.
            /// </summary>
            internal ushort Version;

            /// <summary>
            /// Gets the id of the thread that generated the event.
            /// </summary>
            internal int ThreadId;

            /// <summary>
            /// Gets the id of the process that generated the event.
            /// </summary>
            internal int ProcessId;

            /// <summary>
            /// Gets the  time the event occurred. The resolution depends on the value
            /// of the <see href="http://msdn.microsoft.com/en-us/library/aa364160(v=vs.85).aspx">ClientContext</see> of
            /// the WNODE_HEADER member of the EVENT_TRACE_PROPERTIES structure when the controller created the session.
            /// </summary>
            internal long TimeStamp;          // Offset 0x10

            /// <summary>
            /// Gets the Event trace class GUID. You can use the class GUID to identify
            /// a category of events and the Class.EventType member to identify an event within
            /// the category of events.
            /// </summary>
            internal Guid Guid;

            /// <summary>
            /// Gets the elapsed execution time for kernel-mode instructions, in CPU time
            /// units. If you are using a private model, use the value in the ProcessorTime
            /// member instead.
            /// </summary>
            internal int KernelTime;         // Offset 0x28

            /// <summary>
            /// Gets the elapsed execution time for user-mode instructions, in CPU time units.
            /// If you are using a private model, use the value in the ProcessorTime member
            /// instead.
            /// </summary>
            internal int UserTime;
        }

        /// <summary>
        /// Managed version of EVENT_TRACE, it represents a single ETW event.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct EventTrace
        {
            /// <summary>
            /// Gets an EVENT_TRACE_HEADER structure that contains standard event
            /// tracing information.
            /// </summary>
            internal EventTraceHeader Header;

            /// <summary>
            /// Gets the instance identifier. Contains valid data when the provider
            /// calls the TraceEventInstance function to generate the event.
            /// Otherwise, the value is zero.
            /// </summary>
            internal uint InstanceId;

            /// <summary>
            /// Gets the instance identifier for a parent event. Contains valid data
            /// when the provider calls the TraceEventInstance function to generate
            /// the event. Otherwise, the value is zero.
            /// </summary>
            internal uint ParentInstanceId;

            /// <summary>
            /// Gets the GUID of the parent event. Contains valid data when the provider
            /// calls the TraceEventInstance function to generate the event.
            /// Otherwise, the value is zero.
            /// </summary>
            internal Guid ParentGuid;

            /// <summary>
            /// Gets the pointer to the beginning of the event-specific data for this event.
            /// </summary>
            internal IntPtr MofData; // PVOID

            /// <summary>
            /// Gets the number of bytes pointed by <see cref="MofData"/>.
            /// </summary>
            internal int MofLength;

            /// <summary>
            /// Gets information about the event such as the model identifier and
            /// processor number of the CPU on which the provider process ran.
            /// </summary>
            internal EtwBufferContext BufferContext;
        }

        /// <summary>
        /// Managed version of EVENT_TRACE_LOGFILEW. This is the main struct passed to OpenTrace().
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct EventTraceLogfilew
        {
            /// <summary>
            /// Gets the name of the log file used by the event tracing model.
            /// </summary>
            [MarshalAs(UnmanagedType.LPWStr)]
            internal string LogFileName;

            /// <summary>
            /// Gets the name of the event tracing model.
            /// </summary>
            [MarshalAs(UnmanagedType.LPWStr)]
            internal string LoggerName;

            /// <summary>
            /// Gets the current time, in 100-nanosecond intervals since midnight, January 1, 1601.
            /// </summary>
            internal long CurrentTime;

            /// <summary>
            /// Gets the number of buffers processed.
            /// </summary>
            internal uint BuffersRead;

            /// <summary>
            /// Gets the log file mode (real time or trace) to be used.
            /// </summary>
            internal uint LogFileMode;

            /// <summary>
            /// Gets, on output, an EVENT_TRACE structure that contains the last event processed.
            /// </summary>
            /// <remarks>
            /// EVENT_TRACE for the current event.  Nulled-out when we are opening files.
            /// </remarks>
            internal EventTrace CurrentEvent;

            /// <summary>
            /// Gets, on output, a TRACE_LOGFILE_HEADER structure that contains general
            /// information about the model and the computer on which the model ran.
            /// </summary>
            internal TraceLogfileHeader LogfileHeader;

            /// <summary>
            /// Gets the pointer to the BufferCallback function that receives buffer-related
            /// statistics for each buffer ETW flushes. ETW calls this callback after it delivers
            /// all the events in the buffer. This callback is optional.
            /// </summary>
            internal EventTraceBufferCallback BufferCallback;

            /// <summary>
            /// Gets, on output, the size of each buffer, in bytes.
            /// </summary>
            internal int BufferSize;

            /// <summary>
            /// Gets, on output, contains the number of bytes in the buffer that contain valid information.
            /// </summary>
            internal int Filled;

            /// <summary>
            /// Gets the number of lost events. Currently not used by ETW.
            /// </summary>
            internal int EventsLost;

            /// <summary>
            /// Gets the pointer to the EventCallback function that ETW calls for each event in the buffer.
            /// </summary>
            internal EventRecordCallback EventCallback;

            /// <summary>
            /// Gets a value indicating whether this is a kernel trace or not.
            /// </summary>
            internal int IsKernelTrace;

            /// <summary>
            /// Gets the context data that a consumer can specify when calling OpenTrace.
            /// </summary>
            internal IntPtr Context;
        }

        /// <summary>
        /// Managed declaration for the native TRACE_GUID_INFO structure.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364142(v=vs.85).aspx"/>
        [StructLayout(LayoutKind.Sequential)]
        internal struct TraceGuidInfo
        {
            /// <summary>
            /// The number of TRACE_PROVIDER_INSTANCE_INFO blocks contained in the list. You can have
            /// multiple instances of the same provider if the provider lives in a DLL that is loaded
            /// by multiple processes.
            /// </summary>
            internal uint InstanceCount;

            /// <summary>
            /// Reserved, do not use.
            /// </summary>
            internal uint Reserved;
        }

        /// <summary>
        /// Managed declaration for the native TRACE_PROVIDER_INSTANCE_INFO structure.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364146(v=vs.85).aspx"/>
        [StructLayout(LayoutKind.Sequential)]
        internal struct TraceProviderInstanceInfo
        {
            /// <summary>
            /// Offset, in bytes, from the beginning of this structure to the next TRACE_PROVIDER_INSTANCE_INFO
            /// structure. The value is zero if there is not another instance info block.
            /// </summary>
            internal uint NextOffset;

            /// <summary>
            /// Number of TRACE_ENABLE_INFO structures in this block. Each structure represents a session that
            /// enabled the provider.
            /// </summary>
            internal uint EnableCount;

            /// <summary>
            /// Process identifier of the process that registered the provider.
            /// </summary>
            internal uint Pid;

            /// <summary>
            /// Can be one of the following flags TRACE_PROVIDER_FLAG_LEGACY, i.e.: The provider used
            /// RegisterTraceGuids instead of EventRegister to register itself, or
            /// TRACE_PROVIDER_FLAG_PRE_ENABLE, i.e.: The provider is not registered; however, one or
            /// more sessions have enabled the provider.
            /// </summary>
            internal uint Flags;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SelectionClauseV3.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    /// <summary>
    /// Used to determine how many results should be returned from the query, how they should be ordered, and what criteria to used to determine
    /// which series are included.
    /// </summary>
    public sealed class SelectionClauseV3
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="SelectionClauseV3"/> class.
        /// </summary>
        /// <param name="propertyDefinition">The property used .</param>
        /// <param name="numberOfResultsToReturn">The number of results to return.</param>
        /// <param name="orderBy">The ordering of the selection.</param>
        public SelectionClauseV3(PropertyDefinition propertyDefinition, int numberOfResultsToReturn, OrderBy orderBy)
        {
            this.PropertyDefinition = propertyDefinition;
            this.NumberOfResultsToReturn = numberOfResultsToReturn;
            this.OrderBy = orderBy;
        }

        /// <summary>
        /// Defines which sampling type data is used to determine the top series.
        /// </summary>
        public PropertyDefinition PropertyDefinition { get; }

        /// <summary>
        /// Gets the number of time series to return.
        /// </summary>
        public int NumberOfResultsToReturn { get; }

        /// <summary>
        /// Gets the ordering of the selection
        /// </summary>
        public OrderBy OrderBy { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ILocalMetricReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    /// <summary>
    /// The interface for consumption of locally aggregated metrics or local raw metrics.
    /// </summary>
    public interface ILocalMetricReader
    {
        /// <summary>
        /// Reads the local raw metrics.
        /// </summary>
        /// <param name="metricProducedAction">The action to execute when metric available.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="etlFileName">The name of the etw file from when read data. If null, realtime session will be used.</param>
        /// <returns>An awaitable <see cref="Task"/>.</returns>
        Task ReadLocalRawMetricsAsync(
            Action<ILocalRawMetric> metricProducedAction,
            CancellationToken cancellationToken,
            string etlFileName = null);

        /// <summary>
        /// Reads the locally aggregated metrics.
        /// </summary>
        /// <param name="metricProducedAction">The action to execute when metric available.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="etlFileName">The name of the etw file from when read data. If null, realtime session will be used.</param>
        /// <returns>An awaitable <see cref="Task"/>.</returns>
        Task ReadLocalAggregatedMetricsAsync(
            Action<ILocalAggregatedMetric> metricProducedAction,
            CancellationToken cancellationToken,
            string etlFileName = null);
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricEnrichmentRule.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricEnrichmentRuleManagement
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Represents a metric enrichment rule.
    /// </summary>
    /// <remarks>
    /// TODO: Add a metric enrichment rule builder once we are ready to announce this to customer.
    /// </remarks>
    public sealed class MetricEnrichmentRule
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MetricEnrichmentRule"/> class.
        /// </summary>
        /// <param name="stampId">The stamp identifier this rule belongs too.</param>
        /// <param name="monitoringAccountFilter">
        /// The monitoring account filter to be used for matching applicable rules for a given monitoring account.
        /// Use * to represent Wild card filter.
        /// </param>
        /// <param name="metricNamespaceFilter">
        /// The metric namespace filter to be used for matching applicable rules for a given metric namespace.
        /// Use * to represent Wild card filter.
        /// </param>
        /// <param name="metricNameFilter">
        /// The metric name filter to be used for matching applicable rules for a given metric name.
        /// Use * to represent Wild card filter.
        /// </param>
        /// <param name="transformations">Represents the transformations associated with this rule..</param>
        public MetricEnrichmentRule(string stampId, string monitoringAccountFilter, string metricNamespaceFilter, string metricNameFilter, List<MetricEnrichmentRuleTransformationDefinition> transformations)
        {
            if (string.IsNullOrEmpty(stampId))
            {
                throw new ArgumentNullException(nameof(stampId));
            }

            if (string.IsNullOrEmpty(monitoringAccountFilter))
            {
                throw new ArgumentNullException(nameof(monitoringAccountFilter));
            }

            if (string.IsNullOrEmpty(metricNamespaceFilter))
            {
                throw new ArgumentNullException(nameof(metricNamespaceFilter));
            }

            if (string.IsNullOrEmpty(metricNameFilter))
            {
                throw new ArgumentNullException(nameof(metricNameFilter));
            }

            this.StampId = stampId;
            this.MonitoringAccountFilter = monitoringAccountFilter;
            this.MetricNamespaceFilter = metricNamespaceFilter;
            this.MetricNameFilter = metricNameFilter;
            this.Transformations = transformations;
        }

        /// <summary>
        /// Gets the stamp identifier this rule belongs too.
        /// </summary>
        public string StampId { get; private set; }

        /// <summary>
        /// Gets the filter to be used for matching applicable rules for a given monitoring account.
        /// </summary>
        /// <remarks>
        /// Use * to represent Wild card filter.
        /// </remarks>
        public string MonitoringAccountFilter { get; private set; }

        /// <summary>
        /// Gets the filter to be used for matching applicable rules for a given metric namespace.
        /// </summary>
        /// <remarks>
        /// Use * to represent Wild card filter.
        /// </remarks>
        public string MetricNamespaceFilter { get; private set; }

        /// <summary>
        /// Gets the filter to be used for matching applicable rules for a given metric name.
        /// </summary>
        /// <remarks>
        /// Use * to represent Wild card filter.
        /// </remarks>
        public string MetricNameFilter { get; private set; }

        /// <summary>
        /// Represents the transformations associated with this rule.
        /// </summary>
        public List<MetricEnrichmentRuleTransformationDefinition> Transformations { get; private set; }

        /// <summary>
        /// Validates the data is valid rule.
        /// </summary>
        /// <returns>
        /// Validation failure message, empty means validation passed
        /// </returns>
        internal string Validate()
        {
            if (string.IsNullOrEmpty(this.StampId))
            {
                return "Stamp id cannot be null";
            }

            if (string.IsNullOrEmpty(this.MonitoringAccountFilter))
            {
                return "MonitoringAccountFilter cannot be null";
            }

            if (string.IsNullOrEmpty(this.MetricNamespaceFilter))
            {
                return "MetricNamespaceFilter cannot be null";
            }

            if (string.IsNullOrEmpty(this.MetricNameFilter))
            {
                return "MetricNameFilter cannot be null";
            }

            if (this.Transformations == null || this.Transformations.Count == 0)
            {
                return "Transformations cannot be null or empty";
            }

            foreach (var transformation in this.Transformations)
            {
                var validationFailureMessage = transformation.Validate();
                if (!string.IsNullOrEmpty(validationFailureMessage))
                {
                    return validationFailureMessage;
                }
            }

            return string.Empty;
        }
    }
}
<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="VersionComparer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System.Linq;

    using Microsoft.Cloud.Metrics.Client.Configuration;
    using Microsoft.Cloud.Metrics.Client.Logging;

    using Newtonsoft.Json;

    /// <summary>
    /// The comparer class for configuration versions.
    /// </summary>
    internal static class VersionComparer
    {
        private static readonly object LogId = Logger.CreateCustomLogId("VersionComparer");

        /// <summary>
        /// Compares the metrics version with server.
        /// </summary>
        /// <param name="metricConfigFromFile">The metric configuration from file.</param>
        /// <param name="metricConfigurationOnServer">The metric configuration on server.</param>
        /// <returns>
        /// 1 if configuration from file has a higher version number, -1 if lower, and 0 if identical.
        /// </returns>
        internal static int CompareMetricsVersionWithServer(IMetricConfiguration metricConfigFromFile, IMetricConfiguration metricConfigurationOnServer)
        {
            const string logTag = "CompareMetricsVersionWithServer";
            if (metricConfigurationOnServer == null)
            {
                return 1;
            }

            if (metricConfigFromFile.Version == metricConfigurationOnServer.Version)
            {
                Logger.Log(
                    LoggerLevel.Warning,
                    LogId,
                    logTag,
                    "The version in the file is the same as the one on the server. Skip.");
                return 0;
            }

            if (metricConfigFromFile.Version < metricConfigurationOnServer.Version)
            {
                Logger.Log(
                    LoggerLevel.Warning,
                    LogId,
                    logTag,
                    $"The version {metricConfigFromFile.Version} in the file is less than the one {metricConfigurationOnServer.Version} on the server! Please download the latest version first. Skip.");

                return -1;
            }

            return 1;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IMonitoringAccountAcls.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System.Collections.Generic;

    /// <summary>
    /// Represents a list of ACLs associated with a monitoring account.
    /// </summary>
    /// <remarks>
    /// This does not include AP PKI as the cert is automatically generated and the ACL itself is not needed client side.
    /// </remarks>
    internal interface IMonitoringAccountAcls
    {
        /// <summary>
        /// Gets the thumbprints.
        /// </summary>
        List<string> Thumbprints { get; }

        /// <summary>
        /// Gets the dSMS acls.
        /// </summary>
        List<string> DsmsAcls { get; }

        /// <summary>
        /// Gets the KeyVault acls.
        /// </summary>
        List<string> KeyVaultAcls { get; }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="ClientAssemblyMigrationSerializationBinder.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.Linq;
    using Newtonsoft.Json.Serialization;

    /// <summary>
    /// Binds the client assembly to an assembly known to the server.
    /// </summary>
    internal class ClientAssemblyMigrationSerializationBinder : DefaultSerializationBinder
    {
        private readonly ClientAssemblyMigration[] migrations;

        /// <summary>
        /// Initializes a new instance of the <see cref="ClientAssemblyMigrationSerializationBinder"/> class.
        /// </summary>
        /// <param name="migrations">The migrations.</param>
        public ClientAssemblyMigrationSerializationBinder(ClientAssemblyMigration[] migrations)
        {
            this.migrations = migrations;
        }

        /// <summary>
        /// Determines what type the serialized data should be bound to.
        /// </summary>
        /// <param name="assemblyName">Name of the assembly.</param>
        /// <param name="typeName">Name of the type.</param>
        /// <returns>The type to bind the data to.</returns>
        public override Type BindToType(string assemblyName, string typeName)
        {
            var migration = this.migrations.SingleOrDefault(p => p.FromAssembly == assemblyName && p.FromType == typeName);
            if (migration != null)
            {
                return migration.ToType;
            }

            return base.BindToType(assemblyName, typeName);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UserPermissionV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using Newtonsoft.Json;

    /// <summary>
    /// User with access to MDM.
    /// </summary>
    public sealed class UserPermissionV2 : IPermissionV2
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UserPermissionV2"/> class.
        /// Create a new user for MDM access.
        /// </summary>
        /// <param name="identity">The alias of the user.</param>
        /// <param name="roleConfiguration">The role assigned to this user.</param>
        [JsonConstructor]
        public UserPermissionV2(string identity, RoleConfiguration roleConfiguration)
        {
            this.Identity = identity;
            this.Description = null;
            this.RoleConfiguration = roleConfiguration;
        }

        /// <summary>
        /// The identity to grant permission.
        /// </summary>
        public string Identity { get; }

        /// <inheritdoc />
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string Description { get; }

        /// <summary>
        /// The level of access to be granted to this identity.
        /// </summary>
        public RoleConfiguration RoleConfiguration { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SegmentPurpose.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Metrics.Services.Common.BlobSegment
{
    /// <summary>
    /// The purpose of a segment - used for metric reporting to better understand the memory usage patterns.
    /// Also for debug mode it is possible to store the segment purpose within the segment and check
    /// if wrong segment is released during release operations.
    /// The values MUST start with 0 and increment by 1 (this assumption is made by BlobSegmentPool.PoolStatistics).
    /// the value of BlobSegment.ReleasedSegment cannot be used as SegmentPurpose value
    /// </summary>
    public enum SegmentPurpose
    {
        /// <summary>
        /// The segment purpose is undefined.
        /// </summary>
        Undefined = 0,

        /// <summary>
        /// The segment is created to hold the incoming histogram.
        /// </summary>
        IncomingHistogram = 1,

        /// <summary>
        /// The segment is created to hold the merged histogram arena.
        /// </summary>
        MergedHistogramArena = 2,

        /// <summary>
        /// The segment holds the histogram in CST storage.
        /// </summary>
        HistogramForStorage = 3,

        /// <summary>
        /// The segment is created during the blob growth during write operation.
        /// </summary>
        GrowOnWritePastTheEndOfBlob = 4,

        /// <summary>
        /// The segment that is part of a clone of existing blob (request for data from storage that returns the copy of the data)
        /// </summary>
        BlobClone = 5,

        /// <summary>
        /// The segment that was read from disk file that contains histograms
        /// </summary>
        DiskHistogram = 6,

        /// <summary>
        /// The segment that was read from Azure Table Storage as a result of the query
        /// </summary>
        AzureTableStorageQueryHistogram = 7,

        /// <summary>
        /// The segment that was created for uploading data to Azure Table Storage.
        /// </summary>
        AzureTableStorageUploadHistogram = 8,

        /// <summary>
        /// The segment that was created during histograms aggregation on FE or FTA.
        /// </summary>
        AggregatedHistogram = 9,

        /// <summary>
        /// The segment that was created during the query-time histogram merge.
        /// </summary>
        QueryTimeMergeHistogram = 10,

        /// <summary>
        /// The segment that was created during list of buckets conversion.
        /// </summary>
        ListToBlobConversion = 11,

        /// <summary>
        /// The segment used to read encoded rollup data and then decode it when serving a query from rollups.
        /// </summary>
        RollupDataDecoding = 12,
    }
}
<file_sep>﻿//-----------------------------------------------------------------------
// <copyright file="AccountNotFoundException.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;

    /// <summary>
    /// Exception thrown when account object cannot be found.
    /// </summary>
    public sealed class AccountNotFoundException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AccountNotFoundException"/> class.
        /// </summary>
        /// <param name="message">Message describing exception situation.</param>
        /// <param name="innerException">Inner exception which caused exception situation.</param>
        public AccountNotFoundException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TraceBufferContext.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Type that wraps access to the buffer context fields of an ETW event record.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// Type that wraps access to the buffer context fields of an ETW event record.
    /// </summary>
    internal unsafe struct TraceBufferContext
    {
        /// <summary>
        /// Pointer to the native structure being wrapped.
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2151:FieldsWithCriticalTypesShouldBeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        private readonly NativeMethods.EtwBufferContext* bufferContext;

        /// <summary>
        /// Initializes a new instance of the <see cref="TraceBufferContext"/> struct.
        /// </summary>
        /// <param name="bufferContext">
        /// Pointer to the native structure being wrapped by the instance.
        /// </param>
        public TraceBufferContext(NativeMethods.EtwBufferContext* bufferContext)
        {
            if (bufferContext == null)
            {
                throw new ArgumentNullException("bufferContext");
            }

            this.bufferContext = bufferContext;
        }

        /// <summary>
        /// Gets the number of the CPU on which the provider process was running.
        /// The number is zero on a single processor computer.
        /// </summary>
        public byte ProcessorNumber
        {
            get
            {
                return this.bufferContext->ProcessorNumber;
            }
        }

        /// <summary>
        /// Gets alignment between events (always eight).
        /// </summary>
        public byte Alignment
        {
            get
            {
                return this.bufferContext->Alignment;
            }
        }

        /// <summary>
        /// Gets Identifier of the model that logged the event.
        /// </summary>
        public ushort LoggerId
        {
            get
            {
                return this.bufferContext->LoggerId;
            }
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="ComputedSamplingTypeConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// Represents the configuration of computed sampling type.
    /// </summary>
    public class ComputedSamplingTypeConfiguration
    {
        /// <summary>
        /// This is the expression used by the build-in "Average" sampling type.
        /// </summary>
        public const string AverageExpression = @"raw.Sum / (raw.Count || 1)";

        /// <summary>
        /// This is the expression used by the build-in "NullableAverage" sampling type.
        /// </summary>
        public const string NullableAverageExpression = @"raw.Count ? (raw.Sum / raw.Count) : null";

        /// <summary>
        /// This is the expression used by the build-in "Rate" sampling type.
        /// </summary>
        public const string RateExpression = @"raw.Sum / 60";

        /// <summary>
        /// This is the expression used by the build-in "Standard deviation" sampling type.
        /// </summary>
        public const string StandardDeviationExpression = @"raw.Count < 2 ? null : Math.sqrt(raw.SumOfSquareDiffFromMean / raw.Count)";

        /// <summary>
        /// Gets or sets the friendly name for the computed metric, e.g.: Successful calls, CPU utilization, etc.
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the JavaScript expression used to produce this metric from raw values.
        /// </summary>
        public string Expression { get; set; }

        /// <summary>
        /// Gets or sets the engine to be used to evaluate the expression. If it is null, empty or blank
        /// the engine defaults to the one configured for the appliucation.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string ExpressionEngine { get; set; }

        /// <summary>
        /// Gets or sets the number suffix.
        /// </summary>
        /// <remarks>
        /// This is the unit to show in UI for the data points, such as seconds, ms, %, or any free text that make sense.
        /// </remarks>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string Unit { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the sampling type is added by the system.
        /// </summary>
        /// <remarks>
        /// These sampling type configurations cannot be deleted or modified.
        /// </remarks>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public bool IsBuiltIn { get; set; }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="EventSourceLogEngine.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Logging
{
    using System;
    using System.Diagnostics.Tracing;
    using System.Globalization;

    /// <summary>
    /// Log engine that will emit event source based messages.
    /// </summary>
    /// <remarks>
    /// Class is intentionally left public to allow for invocation of the GenerateManifest method if needed manifested ETW event consumption.
    /// </remarks>
    [EventSource(Name = "Microsoft-MDMetricsClient", Guid = "{FEB9BEAF-6D93-442E-BB78-7F581B618201}")]
    public sealed class EventSourceLogEngine : EventSource, ILogEngine
    {
        /// <summary>
        /// The logger instance
        /// </summary>
        private static readonly Lazy<EventSourceLogEngine> Instance = new Lazy<EventSourceLogEngine>(() => new EventSourceLogEngine());

        /// <summary>
        /// Prevents a default instance of the <see cref="EventSourceLogEngine"/> class from being created.
        /// </summary>
        private EventSourceLogEngine()
        {
            // Do nothing.
        }

        /// <summary>
        /// Gets the logger instance.
        /// </summary>
        public static EventSourceLogEngine Logger
        {
            get { return Instance.Value; }
        }

        /// <summary>
        /// Logs the given data according to the engine implementation.
        /// </summary>
        /// <param name="level">Level of the log statement.</param>
        /// <param name="logId">Log identification for classifying log statements.</param>
        /// <param name="tag">Extra string that allows another level of classification under the log id.</param>
        /// <param name="format">Message to be logged, it can be a format message.</param>
        /// <param name="objectParams">Optional, any parameter to be used to build the formatted message string.</param>
        public void Log(LoggerLevel level, object logId, string tag, string format, params object[] objectParams)
        {
            if (this.IsLogged(level, logId, tag))
            {
                var intermediateFormat = string.Format(
                    CultureInfo.InvariantCulture,
                    "Level=[{0}] LogId=[{1}] Tag=[{2}] {3}",
                    level,
                    logId,
                    tag,
                    format);

                var finalMessage = string.Format(CultureInfo.InvariantCulture, intermediateFormat, objectParams);

                switch (level)
                {
                    case LoggerLevel.Debug:
                        this.EventSourceLogDebug(finalMessage);
                        break;
                    case LoggerLevel.Info:
                        this.EventSourceLogInfo(finalMessage);
                        break;
                    case LoggerLevel.Warning:
                        this.EventSourceLogWarning(finalMessage);
                        break;
                    case LoggerLevel.Error:
                        this.EventSourceLogError(finalMessage);
                        break;
                    case LoggerLevel.CustomerFacingInfo:
                        this.EventSourceLogInfo(finalMessage);
                        break;
                }
            }
        }

        /// <summary>
        /// Logs the message via event source at debug level.
        /// </summary>
        /// <param name="message">The message.</param>
        [Event(1, Level = EventLevel.Verbose)]
        public void EventSourceLogDebug(string message)
        {
            this.WriteEvent(1, message);
        }

        /// <summary>
        /// Logs the message via event source at info level.
        /// </summary>
        /// <param name="message">The message.</param>
        [Event(2, Level = EventLevel.Informational)]
        public void EventSourceLogInfo(string message)
        {
            this.WriteEvent(2, message);
        }

        /// <summary>
        /// Logs the message via event source at warning level.
        /// </summary>
        /// <param name="message">The message.</param>
        [Event(3, Level = EventLevel.Warning)]
        public void EventSourceLogWarning(string message)
        {
            this.WriteEvent(3, message);
        }

        /// <summary>
        /// Logs the message via event source at error level.
        /// </summary>
        /// <param name="message">The message.</param>
        [Event(4, Level = EventLevel.Error)]
        public void EventSourceLogError(string message)
        {
            this.WriteEvent(4, message);
        }

        /// <summary>
        /// Checks if a log statement with the given parameters will be actually logged or
        /// not. Useful to avoid expensive operations for log statements that are going to
        /// be dropped by the log engine.
        /// </summary>
        /// <param name="level">Level of the log statement.</param>
        /// <param name="logId">Log identification for classifying log statements.</param>
        /// <param name="tag">Extra string that allows another level of classification under the log id.</param>
        /// <returns>
        /// True if the statement is going to be logged, false otherwise.
        /// </returns>
        public bool IsLogged(LoggerLevel level, object logId, string tag)
        {
            return this.IsEnabled(this.GetEtwLevelFromLogLevel(level), EventKeywords.None);
        }

        /// <summary>
        /// Gets the ETW level from log level.
        /// </summary>
        /// <param name="level">The log level.</param>
        /// <returns>The ETW level</returns>
        private EventLevel GetEtwLevelFromLogLevel(LoggerLevel level)
        {
            switch (level)
            {
                case LoggerLevel.Debug:
                    return EventLevel.Verbose;
                case LoggerLevel.Info:
                    return EventLevel.Informational;
                case LoggerLevel.Warning:
                    return EventLevel.Warning;
                case LoggerLevel.Error:
                    return EventLevel.Error;
            }

            return EventLevel.Informational;
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="DoubleValueSerializer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.IO;
    using System.Runtime.CompilerServices;
    using Metrics.Serialization.BitHelper;

    /// <summary>
    /// This class provides set of methods to serialize double values using gorilla paper algorithm.
    /// </summary>
    public sealed class DoubleValueSerializer
    {
        private const int NumBitsToEncodeNumLeadingZeros = 5;
        private const int NumBitsToEncodeNumMeaningfulBits = 6;
        private const int MaxLeadingZerosLength = (1 << NumBitsToEncodeNumLeadingZeros) - 1;

        private static readonly double[] EmptyDoubleArray = new double[0];
        private static readonly double?[] EmptyNullableDoubleArray = new double?[0];

        /// <summary>
        /// Serializes the specified <paramref name="values"/>.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="values">The values to serialize.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void Serialize(BinaryWriter writer, double[] values)
        {
            unsafe
            {
                if (values.Length > 0)
                {
                    fixed (double* p = &values[0])
                    {
                        Serialize(writer, p, values.Length);
                    }
                }
                else
                {
                    Serialize(writer, null, 0);
                }
            }
        }

        /// <summary>
        /// Serializes the specified <paramref name="values"/>.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="values">The pointer to the array of values to serialize.</param>
        /// <param name="count">The number of elements in values array.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void Serialize(BinaryWriter writer, double* values, int count)
        {
            // Reserve one byte for future versioning.
            writer.Write((byte)1);
            SerializationUtils.WriteUInt32AsBase128(writer, (uint)count);
            if (count > 0)
            {
                BitBinaryWriter bitWriter = new BitBinaryWriter(writer);
                var previousState = new DoubleValueState(0, -1, -1);

                for (int i = 0; i < count; ++i)
                {
                    DoubleValueState newState;
                    WriteDouble(bitWriter, values[i], previousState, out newState);
                    previousState = newState;
                }

                bitWriter.Flush();
            }
        }

        /// <summary>
        /// Deserializes to an array of <see lang="double"/> from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>An array of <see lang="double"/>.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static double[] Deserialize(BinaryReader reader)
        {
            // version not in use yet.
            reader.ReadByte();
            var numOfItems = (int)SerializationUtils.ReadUInt32FromBase128(reader);

            if (numOfItems == 0)
            {
                return EmptyDoubleArray;
            }

            var result = new double[numOfItems];

            unsafe
            {
                fixed (double* p = &result[0])
                {
                    DeserializeValues(reader, p, numOfItems);
                }
            }

            return result;
        }

        /// <summary>
        /// Deserializes to an array of <see lang="double"/> from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="values">The pointer to the array to store the result values.</param>
        /// <param name="expectedCount">The number of elements in values array, this value should match the serialized array size.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void Deserialize(BinaryReader reader, double* values, int expectedCount)
        {
            // version not in use yet.
            reader.ReadByte();
            var count = (int)SerializationUtils.ReadUInt32FromBase128(reader);

            if (count != expectedCount)
            {
                throw new InvalidDataException($"Wrong count in serialized data: expected {expectedCount}, but was {count}");
            }

            DeserializeValues(reader, values, count);
        }

        /// <summary>
        /// Deserializes to an array of nullable <see lang="double"/> from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <returns>An array of nullable <see lang="double"/>.</returns>
        public static double?[] DeserializeToNullableDoubles(BinaryReader reader)
        {
            // version not in use yet.
            reader.ReadByte();
            var numOfItems = (int)SerializationUtils.ReadUInt32FromBase128(reader);

            if (numOfItems == 0)
            {
                return EmptyNullableDoubleArray;
            }

            var result = new double?[numOfItems];

            var bitBinaryReader = new BitBinaryReader(reader);
            var previousState = new DoubleValueState(0, -1, -1);
            for (int i = 0; i < numOfItems; ++i)
            {
                var newState = ReadDouble(bitBinaryReader, previousState);
                result[i] = double.IsNaN(newState.Value) ? (double?)null : newState.Value;
                previousState = newState;
            }

            return result;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static unsafe void DeserializeValues(BinaryReader reader, double* values, int count)
        {
            var bitBinaryReader = new BitBinaryReader(reader);
            var previousState = new DoubleValueState(0, -1, -1);
            for (int i = 0; i < count; ++i)
            {
                var newState = ReadDouble(bitBinaryReader, previousState);
                values[i] = newState.Value;
                previousState = newState;
            }
        }

        /// <summary>
        /// Encodes the given double value to the given writer.
        /// </summary>
        /// <param name="writer">The writer into which value needs to be encoded.</param>
        /// <param name="value">The value to be encoded.</param>
        /// <param name="previousValue">The previous value state.</param>
        /// <param name="newValue">The new value state.</param>
        /// <returns>True if encoding was success else false.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool WriteDouble(BitBinaryWriter writer, double value, DoubleValueState previousValue, out DoubleValueState newValue)
        {
            newValue = previousValue;
            newValue.Value = value;

            var xor = BitConverter.DoubleToInt64Bits(value) ^ BitConverter.DoubleToInt64Bits(previousValue.Value);

            if (xor == 0)
            {
                writer.WriteBit(false);
            }
            else
            {
                writer.WriteBit(true);

                var leadingZeros = (sbyte)BitAggregateMagic.CountLeadingZeros(xor);
                var trailingZeros = (sbyte)BitAggregateMagic.CountTrailingZeros(xor);

                if (leadingZeros > MaxLeadingZerosLength)
                {
                    leadingZeros = MaxLeadingZerosLength;
                }

                int blockSize = 64 - leadingZeros - trailingZeros;
                int expectedSize = NumBitsToEncodeNumLeadingZeros + NumBitsToEncodeNumMeaningfulBits + blockSize;
                int previousBlockInformationSize = 64 - previousValue.TrailingZeros - previousValue.LeadingZeros;

                // The block position is set by the first non-zero XOR value. previousValue.LeadingZeros was initialized to -1s to start with.
                if (previousValue.LeadingZeros > 0 && leadingZeros >= previousValue.LeadingZeros && trailingZeros >= previousValue.TrailingZeros && previousBlockInformationSize < expectedSize)
                {
                    writer.WriteBit(false);

                    // there are at least as many leading zeros and as many trailing zeros as with the previous value, reuse the block position.
                    var numMeaningfulBits = BitAggregateMagic.NumBitsInLongInteger - previousValue.LeadingZeros - previousValue.TrailingZeros;

                    writer.WriteBits(xor, numMeaningfulBits, previousValue.TrailingZeros);
                }
                else
                {
                    // start a new block position
                    writer.WriteBit(true);

                    writer.WriteBits(leadingZeros, NumBitsToEncodeNumLeadingZeros, 0);

                    newValue.LeadingZeros = leadingZeros;

                    var numMeaningfulBits = BitAggregateMagic.NumBitsInLongInteger - leadingZeros - trailingZeros;
                    writer.WriteBits(numMeaningfulBits, NumBitsToEncodeNumMeaningfulBits, 0);

                    newValue.TrailingZeros = trailingZeros;

                    writer.WriteBits(xor, numMeaningfulBits, trailingZeros);
                }
            }

            return true;
        }

        /// <summary>
        /// Decodes the double value from the given reader.
        /// </summary>
        /// <param name="reader">The reader from which double value needs to be decoded.</param>
        /// <param name="state">The state of previous decoding.</param>
        /// <returns>Decoded double value with state.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static DoubleValueState ReadDouble(BitBinaryReader reader, DoubleValueState state)
        {
            var firstBit = reader.ReadBit();
            if (!firstBit)
            {
                return state;
            }

            var secondBit = reader.ReadBit();
            long meaningFulBits;
            if (!secondBit)
            {
                var numBitsToRead = BitAggregateMagic.NumBitsInLongInteger - state.LeadingZeros - state.TrailingZeros;
                meaningFulBits = reader.ReadBits(numBitsToRead);
            }
            else
            {
                // a new block position was started since the number starts with "11".
                state.LeadingZeros = (sbyte)reader.ReadBits(NumBitsToEncodeNumLeadingZeros);
                var numBitsToRead = (sbyte)reader.ReadBits(NumBitsToEncodeNumMeaningfulBits);
                if (numBitsToRead == 0)
                {
                    // The block size is 64 bits which becomes 0 in writing into 6 bits - overflow.
                    // If the block size were indeed 0 bits, the xor value would be 0, and the actual value would be identical to the prior value,
                    // so we would have bailed out early on since firstBit would be 0.
                    numBitsToRead = (sbyte)BitAggregateMagic.NumBitsInLongInteger;
                }

                state.TrailingZeros = (sbyte)(BitAggregateMagic.NumBitsInLongInteger - state.LeadingZeros - numBitsToRead);
                meaningFulBits = reader.ReadBits(numBitsToRead);
            }

            var xor = meaningFulBits << state.TrailingZeros;
            state.Value = BitConverter.Int64BitsToDouble(xor ^ BitConverter.DoubleToInt64Bits(state.Value));
            return state;
        }

        private struct DoubleValueState
        {
            public double Value;
            public sbyte LeadingZeros;
            public sbyte TrailingZeros;

            public DoubleValueState(double value, sbyte leadingZeros, sbyte trailingZeros)
            {
                this.Value = value;
                this.LeadingZeros = leadingZeros;
                this.TrailingZeros = trailingZeros;
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Crc.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System.IO;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// Class for computing CRC
    /// </summary>
    public static class Crc
    {
        private static uint[] crcTable = new uint[256]
        {
        0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
        0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
        0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
        0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
        0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
        0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
        0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
        0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
        0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
        0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
        0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
        0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
        0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
        0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
        0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
        0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
        0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
        0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
        0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
        0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
        0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
        0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
        0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
        0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
        0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
        0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
        0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
        0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
        0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
        0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
        0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
        0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
        0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
        0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
        0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
        0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
        0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
        0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
        0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
        0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
        0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
        0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
        0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
        0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
        0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
        0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
        0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
        0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
        0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
        0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
        0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
        0x2d02ef8d
        };

        /// <summary>
        /// Computes the CRC for given stream of data.
        /// </summary>
        /// <param name="crc">Seed value (usually set to 0)</param>
        /// <param name="data">Data stream.</param>
        /// <param name="count">The number of bytes to process.</param>
        /// <returns>CRC value</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static uint ComputeCrc(uint crc, Stream data, long count)
        {
            crc ^= ~((uint)0);

            for (var i = 0; i < count; i++)
            {
                crc = crcTable[(crc ^ data.ReadByte()) & 0xff] ^ (crc >> 8);
            }

            return ~crc;
        }

        /// <summary>
        /// Computes the CRC for given array of bytes starting at the specified offset till the end of the array.
        /// </summary>
        /// <param name="crc">Seed value (usually set to 0)</param>
        /// <param name="data">Data array.</param>
        /// <param name="offset">Offset in the byte array</param>
        /// <returns>CRC value</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static uint ComputeCrc(uint crc, byte[] data, long offset)
        {
            return ComputeCrc(crc, data, offset, data.Length - offset);
        }

        /// <summary>
        /// Computes the CRC for given part of the array of bytes.
        /// </summary>
        /// <param name="crc">Seed value (usually set to 0)</param>
        /// <param name="data">Data array.</param>
        /// <param name="offset">Offset in the byte array</param>
        /// <param name="count">The number of bytes to process</param>
        /// <returns>CRC value</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static uint ComputeCrc(uint crc, byte[] data, long offset, long count)
        {
            crc ^= ~((uint)0);

            for (var i = offset; i < offset + count; i++)
            {
                crc = crcTable[(crc ^ data[i]) & 0xff] ^ (crc >> 8);
            }

            return ~crc;
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------
// <copyright file="SessionType.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    /// <summary>
    /// Types of ETW sessions that the user can select on the configuration.
    /// </summary>
    /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363784(v=vs.85).aspx"/>
    // ReSharper disable UnusedMember.Global
    internal enum SessionType
    {
        /// <summary>
        /// ETW events are going to be logged to a file.
        /// </summary>
        File = 0,

        /// <summary>
        /// The session will be a real-time one.
        /// </summary>
        Realtime = 1,

        /// <summary>
        /// The session will be private to each process and will be logged to
        /// the respective files.
        /// </summary>
        Private = 2,

        /// <summary>
        /// The session is both a file and real-time session.
        /// </summary>
        /// <remarks>Defined also as the reverse form to facilitate parsing the enumeration value.</remarks>
        FileAndRealtime = 3,

        /// <summary>
        /// The session is both a file and real-time session.
        /// </summary>
        /// <remarks>Defined also as the reverse form to facilitate parsing the enumeration value.</remarks>
        RealtimeAndFile = 3,
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="VersionNotSupportedMetricSerializationException.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// <author email="selavrin">
//     <NAME>
// </author>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;

    /// <summary>
    /// An exception used to report about situation when version of deserializer doesn't support format of package it is used to deserialize.
    /// </summary>
    [Serializable]
    public sealed class VersionNotSupportedMetricSerializationException : MetricSerializationException
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="VersionNotSupportedMetricSerializationException"/> class.
        /// </summary>
        /// <param name="message">A message explaining the cause for exception situation.</param>
        public VersionNotSupportedMetricSerializationException(string message)
            : base(message, null)
        {
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="ComputedSamplingTypeExpression.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using Newtonsoft.Json;

    /// <summary>
    /// Computed sampling type expression.
    /// </summary>
    public sealed class ComputedSamplingTypeExpression : IComputedSamplingTypeExpression
    {
        private string name;
        private string expression;

        /// <summary>
        /// Initializes a new instance of the <see cref="ComputedSamplingTypeExpression"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="unit">The unit.</param>
        public ComputedSamplingTypeExpression(string name, string expression, string unit)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }

            if (string.IsNullOrWhiteSpace(expression))
            {
                throw new ArgumentNullException(nameof(expression));
            }

            this.Name = name;
            this.Expression = expression;
            this.IsBuiltIn = false;
            this.Unit = unit;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ComputedSamplingTypeExpression"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="isBuiltIn">if set to <c>true</c> [is built in].</param>
        /// <param name="unit">The unit.</param>
        [JsonConstructor]
        internal ComputedSamplingTypeExpression(string name, string expression, bool isBuiltIn, string unit)
        {
            this.name = name;
            this.expression = expression;
            this.IsBuiltIn = isBuiltIn;
            this.Unit = unit;
        }

        /// <summary>
        /// Gets or sets the name of the expression.
        /// </summary>
        public string Name
        {
            get
            {
                return this.name;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.name = value;
            }
        }

        /// <summary>
        /// Gets or sets the expression.
        /// </summary>
        public string Expression
        {
            get
            {
                return this.expression;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.expression = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is built in.
        /// </summary>
        public bool IsBuiltIn { get; internal set; }

        /// <summary>
        /// Gets or sets the unit.
        /// </summary>
        public string Unit { get; set; }
    }
}
<file_sep>﻿// <copyright file="RawPreaggregateFilterQueryArguments.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

// ReSharper disable once CheckNamespace
namespace Microsoft.Cloud.Metrics.Client.PreaggregateFiltersManagement
{
    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;

    /// <summary>
    /// Represents the arguments for retrieving raw preaggregate filters.
    /// </summary>
    [JsonObject]
    internal sealed class RawPreaggregateFilterQueryArguments
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="RawPreaggregateFilterQueryArguments"/> class.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="preaggregateDimensionNames">The preaggregate dimension names.</param>
        /// <param name="count">The count of filters requested. Use 0 to denote all filters to be returned.</param>
        /// <param name="offset">The offset of the requested filters page calculated based on the count of data returned.</param>
        [JsonConstructor]
        public RawPreaggregateFilterQueryArguments(
            string monitoringAccount,
            string metricNamespace,
            string metricName,
            IEnumerable<string> preaggregateDimensionNames,
            int count,
            int offset)
        {
            if (string.IsNullOrEmpty(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrEmpty(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrEmpty(metricName))
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            if (preaggregateDimensionNames == null)
            {
                throw new ArgumentNullException(nameof(preaggregateDimensionNames));
            }

            if (count < 0)
            {
                throw new ArgumentException($"{nameof(count)} cannot be negative number");
            }

            if (offset < 0)
            {
                throw new ArgumentException($"{nameof(offset)} cannot be negative number");
            }

            this.MonitoringAccount = monitoringAccount;
            this.MetricNamespace = metricNamespace;
            this.MetricName = metricName;
            this.PreaggregateDimensionNames = new SortedSet<string>(preaggregateDimensionNames, StringComparer.OrdinalIgnoreCase);

            foreach (string dim in this.PreaggregateDimensionNames)
            {
                if (string.IsNullOrEmpty(dim))
                {
                    throw new ArgumentException($"{nameof(preaggregateDimensionNames)} cannot have empty of null values");
                }
            }

            this.Count = count;
            this.Offset = offset;
        }

        /// <summary>
        /// Gets or sets the monitoring account.
        /// </summary>
        public string MonitoringAccount { get; }

        /// <summary>
        /// Gets or sets the metric namespace.
        /// </summary>
        public string MetricNamespace { get; }

        /// <summary>
        /// Gets or sets the name of the metric.
        /// </summary>
        public string MetricName { get; }

        /// <summary>
        /// Gets or sets the preaggregate dimension names.
        /// </summary>
        public SortedSet<string> PreaggregateDimensionNames { get; }

        /// <summary>
        /// Gets the count of filters requested.
        /// </summary>
        public int Count { get; }

        /// <summary>
        /// Gets the offset of the requested filters page calculated based on the count of data returned.
        /// </summary>
        public int Offset { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DiagnosticInfo.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    /// <summary>
    /// A class hosting all diagnostic info for customers to send us to help troubleshooting.
    /// </summary>
    public sealed class DiagnosticInfo
    {
        /// <summary>
        /// Gets the trace ID.
        /// </summary>
        public string TraceId { get; internal set; }

        /// <summary>
        /// Gets the handling server identifier.
        /// </summary>
        public string HandlingServerId { get; internal set; }

        /// <summary>
        /// Gets the error message.
        /// </summary>
        public string ErrorMessage { get; internal set; }

        /// <summary>
        /// Returns a <see cref="string" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="string" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return $"TraceId:{this.TraceId}, HandlingServerId:{this.HandlingServerId}, ErrorMessage:{this.ErrorMessage}.";
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IRawMetricConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;
    using Metrics;

    /// <summary>
    /// Represents a raw metric in MDM.
    /// </summary>
    public interface IRawMetricConfiguration : IMetricConfiguration
    {
        /// <summary>
        /// Gets or sets the scaling factor.
        /// </summary>
        float? ScalingFactor { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether client publication is enabled.
        /// </summary>
        bool EnableClientPublication { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether client forking is enabled.
        /// </summary>
        bool EnableClientForking { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the metric will be published to the aggregated ETW channel.
        /// </summary>
        bool EnableClientEtwPublication { get; set; }

        /// <summary>
        /// Gets the raw sampling types (Sum, Count, or legacy MetricsClient sampling types).
        /// </summary>
        IEnumerable<SamplingType> RawSamplingTypes { get; }

        /// <summary>
        /// Gets the preaggregations of the metric.
        /// </summary>
        IEnumerable<IPreaggregation> Preaggregations { get; }

        /// <summary>
        /// Gets the dimensions of the metric.
        /// </summary>
        IEnumerable<string> Dimensions { get; }

        /// <summary>
        /// Gets the computed sampling types.
        /// </summary>
        IEnumerable<IComputedSamplingTypeExpression> ComputedSamplingTypes { get; }

        /// <summary>
        /// Gets or sets a value indicating whether only the last value seen for a time series is perserved on the client.
        /// </summary>
        /// <value>
        /// <c>true</c> if last sampling mode is used; otherwise, <c>false</c>.
        /// </value>
        /// <remarks>
        /// Client side last sampling mode means that within the collection interval (1m) only the last value set to the metric is kept.  This means Sum == Min == Max
        /// and Count == 1 for this metric when it is sent to the server.
        /// </remarks>
        bool EnableClientSideLastSamplingMode { get; }

        /// <summary>
        /// Determines whether this instance can add preaggregation to the metric configuration.
        /// </summary>
        /// <param name="preaggregationToAdd">The preaggregation to add.</param>
        /// <returns>True if the preaggregation can be added.</returns>
        bool CanAddPreaggregation(IPreaggregation preaggregationToAdd);

        /// <summary>
        /// Adds the preaggregate.
        /// </summary>
        /// <param name="preaggregate">The preaggregate.</param>
        void AddPreaggregation(IPreaggregation preaggregate);

        /// <summary>
        /// Removes the preaggregate.
        /// </summary>
        /// <param name="preaggregateName">The name of the preaggregate.</param>
        void RemovePreaggregation(string preaggregateName);

        /// <summary>
        /// Adds the type of the computed sampling.
        /// </summary>
        /// <param name="computedSamplingType">Type of the computed sampling.</param>
        void AddComputedSamplingType(IComputedSamplingTypeExpression computedSamplingType);

        /// <summary>
        /// Removes the type of the computed sampling.
        /// </summary>
        /// <param name="computedSamplingTypeName">Name of the computed sampling type.</param>
        void RemoveComputedSamplingType(string computedSamplingTypeName);
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IReadOnlyHistogram.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// <author email="selavrin">
//     <NAME>
// </author>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System.Collections.Generic;

    /// <summary>
    /// Read-only interface for objects representing histograms.
    /// </summary>
    public interface IReadOnlyHistogram
    {
        /// <summary>
        /// Gets the number of samples in the histogram.
        /// </summary>
        int SamplesCount { get; }

        /// <summary>
        /// Gets the list of histogram samples: ordered pairs of value-count.
        /// </summary>
        IEnumerable<KeyValuePair<ulong, uint>> Samples
        {
            get;
        }

        /// <summary>
        /// Calculates percentile from the histogram.
        /// </summary>
        /// <param name="percent">Percent value for which to calculate percentile.</param>
        /// <returns>Percentile value.</returns>
        float GetPercentile(float percent);
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="EtwPayloadManipulationUtils.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    /// <summary>
    /// This class contains functions used for metrics data serialization/deserialization to/from the ETW payload.
    /// </summary>
    internal static unsafe class EtwPayloadManipulationUtils
    {
        /// <summary>
        /// Equivalent of + operator for IntPtr.
        /// </summary>
        /// <param name="ptr">Pointer value.</param>
        /// <param name="offset">Offset to add.</param>
        /// <returns>Incremented pointer value.</returns>
        public static IntPtr Shift(IntPtr ptr, int offset)
        {
            return new IntPtr(ptr.ToInt64() + offset);
        }

        /// <summary>
        /// Writes a string in UTF8 format to a buffer starting from the specified pointer.
        /// The format is: string size in bytes (int), UTF8 string bytes.
        /// </summary>
        /// <param name="value">String value.</param>
        /// <param name="pointerInPayload">Pointer to a buffer.</param>
        /// <param name="bytesBuffer">Buffer to use during string encoding.</param>
        /// <returns>A pointer shifted by number of bytes written to a buffer.</returns>
        public static IntPtr WriteString(string value, IntPtr pointerInPayload, byte[] bytesBuffer)
        {
            var bytesCount = Encoding.UTF8.GetBytes(value, 0, value.Length, bytesBuffer, 0);
            *((ushort*)pointerInPayload) = (ushort)bytesCount;
            pointerInPayload = new IntPtr(pointerInPayload.ToInt64() + sizeof(ushort));
            Marshal.Copy(bytesBuffer, 0, pointerInPayload, bytesCount);
            return new IntPtr(pointerInPayload.ToInt64() + bytesCount);
        }

        /// <summary>
        /// Reads string value encoded in buffer in UTF8 format.
        /// </summary>
        /// <param name="pointerInPayload">A pointer to a buffer where string bytes are stored.
        /// It will be updated to offset equal to number of bytes occupied by the string.</param>
        /// <returns>String values read.</returns>
        public static string ReadString(ref IntPtr pointerInPayload)
        {
            ushort strLen = *((ushort*)pointerInPayload);
            pointerInPayload = new IntPtr(pointerInPayload.ToInt64() + sizeof(ushort));
            var stringOnPayload = new string((sbyte*)pointerInPayload, 0, strLen, Encoding.UTF8);
            pointerInPayload = new IntPtr(pointerInPayload.ToInt64() + strLen);
            return stringOnPayload;
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LocalAggregatedMetric.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;

    using Etw;
    using Online.Metrics.Serialization;

    /// <summary>
    /// The class representing the locally aggregated metric in the ETW stream.
    /// </summary>
    internal sealed class LocalAggregatedMetric : ILocalAggregatedMetric
    {
        /// <summary>
        /// Represents the character used to separate items within a list stored in a single ETW field.
        /// </summary>
        private static readonly char[] EtwListSeparatorChar = { '^' };

        /// <summary>
        /// The dimension name and dimension value pairs.
        /// </summary>
        private readonly Dictionary<string, string> dimensions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// Gets the Monitoring Account to which this metric is reported.
        /// </summary>
        public string MonitoringAccount { get; private set; }

        /// <summary>
        /// Gets the metric namespace.
        /// </summary>
        public string MetricNamespace { get; private set; }

        /// <summary>
        /// Gets the metric name.
        /// </summary>
        public string MetricName { get; private set; }

        /// <summary>
        /// Gets the time in UTC when metric was reported.
        /// </summary>
        public DateTime MetricTimeUtc { get; private set; }

        /// <summary>
        /// Gets the dimension name-value dictionary.
        /// </summary>
        /// <remarks>The dimension names are case insensitive.</remarks>
        public IReadOnlyDictionary<string, string> Dimensions
        {
            get
            {
                return this.dimensions;
            }
        }

        /// <summary>
        /// Gets the scaling factor applied to metric values.
        /// </summary>
        public float ScalingFactor { get; private set; }

        /// <summary>
        /// Gets the number of samples for which this metric is reported.
        /// </summary>
        public uint Count { get; private set; }

        /// <summary>
        /// Gets the scaled sum of sample values reported this metric.
        /// </summary>
        public float ScaledSum { get; private set; }

        /// <summary>
        /// Gets the scaled minimum value of samples reported this metric.
        /// </summary>
        public float ScaledMin { get; private set; }

        /// <summary>
        /// Gets the scaled maximum value of samples reported this metric.
        /// </summary>
        public float ScaledMax { get; private set; }

        /// <summary>
        /// Gets the sum of sample values reported this metric.
        /// </summary>
        public ulong Sum { get; private set; }

        /// <summary>
        /// Gets the minimum value of samples reported this metric.
        /// </summary>
        public ulong Min { get; private set; }

        /// <summary>
        /// Gets the maximum value of samples reported this metric.
        /// </summary>
        public ulong Max { get; private set; }

        /// <summary>
        /// Converts content of the ETW event published by ME to a <see cref="LocalAggregatedMetric"/>
        /// </summary>
        /// <param name="etwMetricData">Object containing information about metric data sample.</param>
        /// <returns>A MetricData object representing a locally aggregated metric.</returns>
        internal static unsafe LocalAggregatedMetric ConvertToMetricData(NativeMethods.EventRecord* etwMetricData)
        {
            var metricData = new LocalAggregatedMetric();

            IntPtr pointerInPayload = etwMetricData->UserData;
            metricData.MonitoringAccount = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);
            metricData.MetricNamespace = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);
            metricData.MetricName = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);

            long timestamp = *((long*)pointerInPayload);
            metricData.MetricTimeUtc = DateTime.FromFileTimeUtc(timestamp);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(long));

            // Read the dimension name and values and split them out.
            var dimensionNames = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);
            var dimensionValues = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);

            if (!string.IsNullOrWhiteSpace(dimensionNames) && !string.IsNullOrWhiteSpace(dimensionValues))
            {
                var splitDimensionNames = dimensionNames.Split(EtwListSeparatorChar, StringSplitOptions.None);
                var splitDimensionValues = dimensionValues.Split(EtwListSeparatorChar, StringSplitOptions.None);

                // Expected that both lengths be the same since they are written this way.
                for (var x = 0; x < splitDimensionNames.Length && x < splitDimensionValues.Length; ++x)
                {
                    metricData.dimensions[splitDimensionNames[x]] = splitDimensionValues[x];
                }
            }

            var scalingFactor = *((float*)pointerInPayload);
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(float));
            metricData.ScalingFactor = scalingFactor;

            var samplingTypes = (SamplingTypes)(*((int*)pointerInPayload));
            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(int));

            if ((samplingTypes & SamplingTypes.Min) != 0)
            {
                metricData.Min = *((ulong*)pointerInPayload);
                metricData.ScaledMin = metricData.Min / scalingFactor;
            }

            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(ulong));

            if ((samplingTypes & SamplingTypes.Max) != 0)
            {
                metricData.Max = *((ulong*)pointerInPayload);
                metricData.ScaledMax = metricData.Max / scalingFactor;
            }

            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(ulong));

            if ((samplingTypes & SamplingTypes.Sum) != 0)
            {
                metricData.Sum = *((ulong*)pointerInPayload);
                metricData.ScaledSum = metricData.Sum / scalingFactor;
            }

            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(ulong));

            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(float));

            if ((samplingTypes & SamplingTypes.Count) != 0)
            {
                metricData.Count = *((uint*)pointerInPayload);
            }

            pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(uint));

            return metricData;
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DimensionFilter.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using Newtonsoft.Json;

    /// <summary>
    /// A filter to include only specific dimension values, if any.
    /// </summary>
    public sealed class DimensionFilter
    {
        /// <summary>
        /// The dimension name.
        /// </summary>
        private readonly string dimensionName;

        /// <summary>
        /// The dimension values.
        /// </summary>
        private readonly string[] dimensionValues;

        /// <summary>
        /// Flag to indicate if this is exclude filter.
        /// </summary>
        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Reviewed. Suppression is OK here.")]
        private readonly bool isExcludeFilter;

        /// <summary>
        /// Initializes a new instance of the <see cref="DimensionFilter" /> class.
        /// </summary>
        /// <param name="dimensionName">The dimension name.</param>
        /// <param name="dimensionValues">The dimension values.</param>
        /// <param name="isExcludeFilter">If set to <c>true</c> [is exclude filter].</param>
        /// <remarks>
        /// By default, this is an include filter.
        /// </remarks>
        [JsonConstructor]
        public DimensionFilter(string dimensionName, IEnumerable<string> dimensionValues, bool isExcludeFilter)
        {
            if (string.IsNullOrWhiteSpace(dimensionName))
            {
                throw new ArgumentException("dimensionName is null or empty");
            }

            this.dimensionName = dimensionName;

            this.dimensionValues = dimensionValues != null ? dimensionValues.ToArray() : null;

            this.isExcludeFilter = isExcludeFilter;
        }

        /// <summary>
        /// Gets the dimension name.
        /// </summary>
        public string DimensionName
        {
            get
            {
                return this.dimensionName;
            }
        }

        /// <summary>
        /// The dimension values.
        /// </summary>
        public IReadOnlyList<string> DimensionValues
        {
            get
            {
                return this.dimensionValues;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is an exclude dimension filter.
        /// </summary>
        public bool IsExcludeFilter
        {
            get
            {
                return this.isExcludeFilter;
            }
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="string"/> to <see cref="DimensionFilter"/>.
        /// </summary>
        /// <param name="dimensionName">The dimension name.</param>
        /// <returns>
        /// The result of the conversion.
        /// </returns>
        public static implicit operator DimensionFilter(string dimensionName)
        {
            return CreateIncludeFilter(dimensionName);
        }

        /// <summary>
        /// Creates an include dimension filter.
        /// </summary>
        /// <param name="dimensionName">The dimension name.</param>
        /// <param name="dimensionValues">The dimension values.</param>
        /// <returns>An include dimension filter.</returns>
        public static DimensionFilter CreateIncludeFilter(string dimensionName, params string[] dimensionValues)
        {
            return CreateIncludeFilter(dimensionName, dimensionValues.AsEnumerable());
        }

        /// <summary>
        /// Creates an include dimension filter.
        /// </summary>
        /// <param name="dimensionName">The dimension name.</param>
        /// <param name="dimensionValues">The dimension values.</param>
        /// <returns>An include dimension filter.</returns>
        public static DimensionFilter CreateIncludeFilter(string dimensionName, IEnumerable<string> dimensionValues)
        {
            return new DimensionFilter(dimensionName, dimensionValues, isExcludeFilter: false);
        }

        /// <summary>
        /// Creates an exclude dimension filter.
        /// </summary>
        /// <param name="dimensionName">The dimension name.</param>
        /// <param name="dimensionValues">The dimension values.</param>
        /// <returns>An exclude dimension filter.</returns>
        public static DimensionFilter CreateExcludeFilter(string dimensionName, params string[] dimensionValues)
        {
            return CreateExcludeFilter(dimensionName, dimensionValues.AsEnumerable());
        }

        /// <summary>
        /// Creates an exclude dimension filter.
        /// </summary>
        /// <param name="dimensionName">The dimension name.</param>
        /// <param name="dimensionValues">The dimension values.</param>
        /// <returns>An exclude dimension filter.</returns>
        public static DimensionFilter CreateExcludeFilter(string dimensionName, IEnumerable<string> dimensionValues)
        {
            return new DimensionFilter(dimensionName, dimensionValues, isExcludeFilter: true);
        }
    }
}<file_sep>using k8s;
using System.Collections.Generic;

namespace AzSignalR.Monitor.JobRegistry
{
    /// <summary>
    /// A workaround for the connection leak issue in ServiceClient (base of Kubernetes).
    ///
    /// see: https://github.com/Azure/azure-sdk-for-net/issues/5977
    /// </summary>
    class FixedKubernetes : Kubernetes
    {
        public FixedKubernetes(KubernetesClientConfiguration config) : base(config) { }

        protected override void Dispose(bool disposing)
        {
            HttpClientHandler?.Dispose();

            // base.Dispose will set the HttpClientHandler to null.
            base.Dispose(disposing);
        }
    }

    public class KubernetesClientCache
    {
        private readonly IDictionary<string, KubernetesClientWrapper> _instances = new Dictionary<string, KubernetesClientWrapper>();

        public Kubernetes Get(string cluster, string kubeConfig)
        {
            if (_instances.TryGetValue(cluster, out var wrapper))
            {
                if (wrapper.KubeConfig == kubeConfig)
                {
                    return wrapper.Client;
                }
            }
            lock (_instances)
            {
                if (_instances.TryGetValue(cluster, out wrapper))
                {
                    if (wrapper.KubeConfig == kubeConfig)
                    {
                        return wrapper.Client;
                    }
                }
                try
                {
                    using (var kubeConfigStream = Utils.GenerateStreamFromString(kubeConfig))
                    {
                        var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigStream);
                        var client = new FixedKubernetes(config);
                        _instances.Add(cluster, new KubernetesClientWrapper(kubeConfig, client));
                        return client;
                    }
                }
                finally
                {
                    if (wrapper != null)
                    {
                        // this won't clean all the resources, that's why we need the Cache impl here
                        wrapper.Client?.Dispose();
                    }
                }
            }
        }

        class KubernetesClientWrapper
        {
            public string KubeConfig { get; }
            public Kubernetes Client { get; }

            public KubernetesClientWrapper(string config, Kubernetes client)
            {
                KubeConfig = config;
                Client = client;
            }
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="IFrontEndMetricBuilder.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    /// <summary>
    /// The interface used by deserializer to abstract the way how deserialized objects are created.
    /// It is up to the user of the deserializer to implement type to which data are deserialized, use object pools or not etc.
    /// </summary>
    /// <typeparam name="TMetadata">Type of metadata to use for creation of metric data objects.</typeparam>
    public interface IFrontEndMetricBuilder<TMetadata>
        where TMetadata : IMetricMetadata
    {
        /// <summary>
        /// Sets the packet serialization version.
        /// </summary>
        /// <param name="serializationVersion">Serialization version.</param>
        void SetSerializationVersion(ushort serializationVersion);

        /// <summary>
        /// Gets interned string for given value.
        /// </summary>
        /// <param name="value">String to be interned.</param>
        /// <returns>Interned string.</returns>
        string GetString(string value);

        /// <summary>
        /// Creates the custom object representing metric metadata. Note that the same metadata object can be shared
        /// by many metric instances.
        /// </summary>
        /// <param name="metricNamespace">Namespace of the metric.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="dimensionNames">Names of the metric dimensions.</param>
        /// <returns>The metric metadata representing the given parameters.</returns>
        TMetadata CreateMetadata(string metricNamespace, string metricName, IReadOnlyList<string> dimensionNames);

        /// <summary>
        /// Start adding metric data with populating common values.
        /// </summary>
        /// <param name="metadata">Metric metadata.</param>
        /// <param name="dimensionValues">List of dimension values.</param>
        /// <param name="timeUtc">Time of the metric.</param>
        /// <param name="samplingTypes">Available sampling types of the metric.</param>
        /// <param name="count">Count value of the metric (how many times it was reported).</param>
        /// <param name="sum">Sum value of the metric.</param>
        /// <param name="min">Minimum value of the metric.</param>
        /// <param name="max">Maximum value of the metric.</param>
        /// <param name="sumOfSquareDiffFromMean">Sum of squares differences from mean of the metric.</param>
        void BeginMetricCreation(
            TMetadata metadata,
            IReadOnlyList<string> dimensionValues,
            DateTime timeUtc,
            SamplingTypes samplingTypes,
            uint count,
            MetricValueV2 sum,
            MetricValueV2 min,
            MetricValueV2 max,
            double sumOfSquareDiffFromMean);

        /// <summary>
        /// Assigns a histogram to the metric being built.
        /// </summary>
        /// <param name="value">Histogram of the metric.</param>
        void AssignHistogram(IReadOnlyList<KeyValuePair<ulong, uint>> value);

        /// <summary>
        /// Assigns a tdigest to the metric being built
        /// </summary>
        /// <param name="reader">Reader containing the data.</param>
        /// <param name="length">Length of data to read.</param>
        void AssignTDigest(BinaryReader reader, int length);

        /// <summary>
        /// Assigns hyperloglogsketches to the metric being built.
        /// </summary>
        /// <param name="reader">Stream containing the data.</param>
        /// <param name="length">Length of data to read.</param>
        void AssignHyperLogLogSketch(BinaryReader reader, int length);

        /// <summary>
        /// Signals that deserializer has completed metric deserialization.
        /// </summary>
        void EndMetricCreation();
    }
}
<file_sep>﻿//---------------------------------------------------------------------------------
// <copyright file="CollectorConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    /// <summary>
    /// Type to read the configuration of a single collector.
    /// </summary>
    internal sealed class CollectorConfiguration
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CollectorConfiguration" /> class.
        /// </summary>
        /// <param name="etwSessionsPrefix">The etw session name prefix.</param>
        public CollectorConfiguration(string etwSessionsPrefix)
        {
            var cpuNum = Environment.ProcessorCount;

            // ETW buffer properties
            this.FlushTimerSec = 0;
            this.MinBufferCount = 2 * cpuNum;
            this.MaxBufferCount = 2 * this.MinBufferCount;
            this.BufferSizeKB = 256;
            this.ClockType = ClockType.System;
            this.SessionType = SessionType.Realtime;
            this.DeprecatedCollector = null;
            this.MaxFileSizeMB = 100;
            this.MaxFileTimeSpan = TimeSpan.FromMinutes(5);
            this.MaxFileCount = 1440;
            this.OriginalName = "Collector";
            this.Name = GetNormalizedSessionName(this.OriginalName, this.SessionType, etwSessionsPrefix);

            // Providers - by default empty
            this.Providers = new Dictionary<Guid, ProviderConfiguration>();
        }

        /// <summary>
        /// Gets the name of the ETW session to be created for this collector. This is the name found in the
        /// configuration file normalized to make possible to identify ETW sessions created by the MonitoringAgent.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the name of the collector as defined by the user in the configuration file.
        /// </summary>
        public string OriginalName { get; private set; }

        /// <summary>
        /// Gets the name of an ETW session that was used by previous versions of the dependent service that
        /// should be stopped when the service starts to use a session provided via the MonitoringAgent.
        /// </summary>
        /// <remarks>
        /// This is important when the dependant service fails to stop the ETW realtime session that was used
        /// before having the service using the session created by the MonitoringAgent. In this case there will
        /// be no listeners for the legacy session and eventually both sessions (the legacy and the one provided
        /// by the MonitoringAgent) will start to drop events.
        /// </remarks>
        public string DeprecatedCollector { get; set; }

        /// <summary>
        /// Gets how often, in seconds, the trace buffers are forcibly flushed.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363784(v=vs.85).aspx"/>
        public int FlushTimerSec { get; set; }

        /// <summary>
        /// Gets the minimum number of buffers to be allocated to the ETW session.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363784(v=vs.85).aspx"/>
        public int MinBufferCount { get; set; }

        /// <summary>
        /// Gets the maximum number of buffers to be allocated to the ETW session.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363784(v=vs.85).aspx"/>
        public int MaxBufferCount { get; set; }

        /// <summary>
        /// Gets the buffer size, in KB, to be used in the session.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363784(v=vs.85).aspx"/>
        public int BufferSizeKB { get; set; }

        /// <summary>
        /// Gets the clock type to be used in the ETW session. Check ClockType to see how this should
        /// be written in the configuration file.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa364160(v=vs.85).aspx"/>
        public ClockType ClockType { get; set; }

        /// <summary>
        /// Gets the logging mode to be used in the ETW session.
        /// </summary>
        /// <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa363784(v=vs.85).aspx"/>
        public SessionType SessionType { get; set; }

        /// <summary>
        /// Gets the maximum size, in megabytes, that an ETL file should be allowed to grow.
        /// </summary>
        public int MaxFileSizeMB { get; set; }

        /// <summary>
        /// Gets the maximum time that each ETL file should cover.
        /// </summary>
        public TimeSpan MaxFileTimeSpan { get; set; }

        /// <summary>
        /// Gets the maximum number of ETL files for this collector that should be allowed
        /// to exist on disk.
        /// </summary>
        public int MaxFileCount { get; set; }

        /// <summary>
        /// Gets the list of provider configurations for the collector.
        /// </summary>
        public Dictionary<Guid, ProviderConfiguration> Providers { get; set; }

        /// <summary>
        /// Gets a normalized name from the session name originally specified in the configuration. The normalized
        /// name will be the ETW session name.
        /// </summary>
        /// <param name="originalName">The session name to be normalized.</param>
        /// <param name="sessionType">The type of the session.</param>
        /// <param name="etwSessionsPrefix">The etw session name prefix.</param>
        /// <returns>
        /// The <see cref="string" /> with the normalized session name.
        /// </returns>
        /// <exception cref="System.IO.InvalidDataException">The specified session type is not recognized:  + sessionType</exception>
        public static string GetNormalizedSessionName(string originalName, SessionType sessionType, string etwSessionsPrefix)
        {
            if (originalName.Equals("NT Kernel Logger", StringComparison.OrdinalIgnoreCase))
            {
                return originalName;
            }

            string sessionTypeAbbr;
            switch (sessionType)
            {
                case SessionType.File:
                    sessionTypeAbbr = "file-";
                    break;
                case SessionType.FileAndRealtime:
                    sessionTypeAbbr = "file+live-";
                    break;
                case SessionType.Private:
                    sessionTypeAbbr = "private-";
                    break;
                case SessionType.Realtime:
                    sessionTypeAbbr = "live-";
                    break;
                default:
                    throw new InvalidDataException(
                        "The specified session type is not recognized: " + sessionType);
            }

            return etwSessionsPrefix + sessionTypeAbbr + originalName;
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Logging;
    using Microsoft.Cloud.Metrics.Client.Metrics;
    using Microsoft.Cloud.Metrics.Client.Utility;
    using Monitors;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    /// <summary>
    /// This class manages get and save operations on metric configurations.
    /// </summary>
    public sealed class MetricConfigurationManager : IMetricConfigurationManager
    {
        private static readonly object LogId = Logger.CreateCustomLogId("MetricConfigurationManager");
        private readonly ConnectionInfo connectionInfo;
        private readonly HttpClient httpClient;
        private readonly string metricConfigurationUrlPrefix;
        private readonly string metricUrlPrefix;
        private readonly JsonSerializerSettings serializerSettings;
        private readonly MonitorConfigurationManager monitorConfigManager;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricConfigurationManager"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the MDM endpoint being used.</param>
        public MetricConfigurationManager(ConnectionInfo connectionInfo)
            : this(connectionInfo, HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo))
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricConfigurationManager"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information for the MDM endpoint being used.</param>
        /// <param name="client">HttpClient for the connection to the MDM endpoint being used.</param>
        internal MetricConfigurationManager(ConnectionInfo connectionInfo, HttpClient client)
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            this.connectionInfo = connectionInfo;
            this.metricConfigurationUrlPrefix = this.connectionInfo.GetAuthRelativeUrl("v1/config/metricConfiguration/");
            this.metricUrlPrefix = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.ConfigRelativeUrl);
            this.monitorConfigManager = new MonitorConfigurationManager(this.connectionInfo);

            this.httpClient = client;

            var migrations = new[]
            {
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.ComputedSamplingTypeExpressionImpl",
                    typeof(ComputedSamplingTypeExpression)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.PreaggregationImpl",
                    typeof(Preaggregation)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.MinMaxConfigurationImpl",
                    typeof(MinMaxConfiguration)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.PercentileConfigurationImpl",
                    typeof(PercentileConfiguration)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.RollupConfigurationImpl",
                    typeof(RollupConfiguration)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.PublicationConfigurationImpl",
                    typeof(PublicationConfiguration)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.DistinctCountConfigurationImpl",
                    typeof(DistinctCountConfiguration)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.RawMetricConfigurationImpl",
                    typeof(RawMetricConfiguration)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.CompositeMetricConfigurationImpl",
                    typeof(CompositeMetricConfiguration)),
                new ClientAssemblyMigration(
                    "Metrics.Server",
                    "Microsoft.Online.Metrics.Server.Utilities.ConfigurationUpdateResult",
                    typeof(ConfigurationUpdateResult)),
                new ClientAssemblyMigration(
                    "Microsoft.Online.Metrics.Common",
                    "Microsoft.Online.Metrics.Common.EventConfiguration.FilteringConfigurationImpl",
                    typeof(FilteringConfiguration)),
            };

            this.serializerSettings = new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = new ClientAssemblyMigrationSerializationBinder(migrations)
            };

            this.MaxParallelRunningTasks = 20;
        }

        /// <summary>
        /// Gets or sets the maximum parallel running tasks.
        /// </summary
        public int MaxParallelRunningTasks { get; set; }

        /// <summary>
        /// Get the metric specified by the account, namespace and metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">The metric name.</param>
        /// <returns>The metric.</returns>
        public async Task<IMetricConfiguration> GetAsync(IMonitoringAccount monitoringAccount, string metricNamespace, string metricName)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrWhiteSpace(metricName))
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            var path = $"{this.metricConfigurationUrlPrefix}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricNamespace)}/metric/{SpecialCharsHelper.EscapeTwice(metricName)}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount.Name))
            {
                Path = path
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Get,
                    this.httpClient,
                    monitoringAccount.Name,
                    this.metricConfigurationUrlPrefix).ConfigureAwait(false);

                return JsonConvert.DeserializeObject<IMetricConfiguration[]>(response.Item1, this.serializerSettings)[0];
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode.HasValue && mce.ResponseStatusCode.Value == HttpStatusCode.NotFound)
                {
                    throw new MetricNotFoundException($"Metric [{monitoringAccount.Name}][{metricNamespace}][{metricName}] not found. TraceId: [{mce.TraceId}]", mce);
                }

                throw;
            }
        }

        /// <summary>
        /// Get the metric specified by the account, namespace and metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="returnEmptyConfig">Determine if empty, unmodified configurations should be returned.</param>
        /// <returns>The metrics that match the criteria.</returns>
        public async Task<IReadOnlyList<IMetricConfiguration>> GetMultipleAsync(IMonitoringAccount monitoringAccount, string metricNamespace, bool returnEmptyConfig = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            var path = $"{this.metricConfigurationUrlPrefix}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricNamespace)}";
            var query = $"includeEmptyConfig={returnEmptyConfig}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount.Name))
            {
                Path = path,
                Query = query
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Get,
                    this.httpClient,
                    monitoringAccount.Name,
                    this.metricConfigurationUrlPrefix).ConfigureAwait(false);

                return JsonConvert.DeserializeObject<IMetricConfiguration[]>(response.Item1, this.serializerSettings);
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode.HasValue && mce.ResponseStatusCode.Value == HttpStatusCode.NotFound)
                {
                    throw new MetricNotFoundException($"Metrics under [{monitoringAccount.Name}][{metricNamespace}] not found. TraceId: [{mce.TraceId}]", mce);
                }

                throw;
            }
        }

        /// <summary>
        /// Get the metric specified by the account, namespace and metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="returnEmptyConfig">Determine if empty, unmodified configurations should be returned.</param>
        /// <returns>The metrics that match the criteria.</returns>
        public async Task<IReadOnlyList<IMetricConfiguration>> GetMultipleAsync(IMonitoringAccount monitoringAccount, bool returnEmptyConfig = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var path = $"{this.metricConfigurationUrlPrefix}/monitoringAccount/{monitoringAccount.Name}";
            var query = $"includeEmptyConfig={returnEmptyConfig}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount.Name))
            {
                Path = path,
                Query = query
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Get,
                    this.httpClient,
                    monitoringAccount.Name,
                    this.metricConfigurationUrlPrefix).ConfigureAwait(false);

                return JsonConvert.DeserializeObject<IMetricConfiguration[]>(response.Item1, this.serializerSettings);
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode.HasValue && mce.ResponseStatusCode.Value == HttpStatusCode.NotFound)
                {
                    throw new MetricNotFoundException($"Metrics under [{monitoringAccount.Name}] not found. TraceId: [{mce.TraceId}]", mce);
                }

                throw;
            }
        }

        /// <summary>
        /// Save the metric configuration provided.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration.</param>
        /// <param name="metricConfiguration">The metric to be saved.</param>
        /// <param name="skipVersionCheck">Flag indicating whether or not the version flag should be honored.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task SaveAsync(IMonitoringAccount monitoringAccount, IMetricConfiguration metricConfiguration, bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (metricConfiguration == null)
            {
                throw new ArgumentNullException(nameof(metricConfiguration));
            }

            var path = $"{this.metricConfigurationUrlPrefix}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricConfiguration.MetricNamespace)}/metric/{SpecialCharsHelper.EscapeTwice(metricConfiguration.Name)}/skipVersionCheck/{skipVersionCheck}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount.Name))
            {
                Path = path,
                Query = "apiVersion=1"
            };

            var serializedMetric = JsonConvert.SerializeObject(new[] { metricConfiguration }, Formatting.Indented, this.serializerSettings);

            try
            {
                await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Post,
                    this.httpClient,
                    monitoringAccount.Name,
                    this.metricConfigurationUrlPrefix,
                    httpContent: serializedMetric).ConfigureAwait(false);
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode == HttpStatusCode.BadRequest)
                {
                    throw new ConfigurationValidationException(
                        $"Metric [{monitoringAccount.Name}][{metricConfiguration.MetricNamespace}][{metricConfiguration.Name}] could not be saved because validation failed. Response: {mce.Message}",
                        ValidationType.ServerSide,
                        mce);
                }

                throw;
            }
        }

        /// <summary>
        /// Deletes the metric configuration by metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <returns>A task the caller can wait on.</returns>
        public async Task DeleteAsync(IMonitoringAccount monitoringAccount, string metricNamespace, string metricName)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrWhiteSpace(metricName))
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            var operation = $"{this.connectionInfo.GetAuthRelativeUrl(string.Empty)}v1/config/metrics";

            var path =
                $"{operation}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricNamespace)}/metric/{SpecialCharsHelper.EscapeTwice(metricName)}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetEndpoint(monitoringAccount.Name))
            {
                Path = path
            };

            await HttpClientHelper.GetResponse(
                uriBuilder.Uri,
                HttpMethod.Delete,
                this.httpClient,
                monitoringAccount.Name,
                operation).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<IConfigurationUpdateResult>> SyncAllAsync(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var metricReader = new MetricReader(this.connectionInfo);

            var namespaces =
                await metricReader.GetNamespacesAsync(monitoringAccount.Name).ConfigureAwait(false);

            IReadOnlyList<IConfigurationUpdateResult> results = null;
            List<string> namespaceswithTimeout = new List<string>();
            foreach (var ns in namespaces)
            {
                try
                {
                    var namespaceResults =
                        await this.SyncAllAsync(monitoringAccount, ns, skipVersionCheck)
                            .ConfigureAwait(false);

                    if (namespaceResults.Any(updateResult => !updateResult.Success))
                    {
                        return namespaceResults;
                    }

                    // For QOS namespaces or other internal namespaces, there is no configuration to
                    // replicate and thus the namespace results is an empty list.
                    if (namespaceResults.Count > 0)
                    {
                        results = namespaceResults;
                    }
                }
                catch (MetricsClientException mce)
                {
                    if (!mce.ResponseStatusCode.HasValue || mce.ResponseStatusCode == HttpStatusCode.GatewayTimeout)
                    {
                        namespaceswithTimeout.Add(ns);
                    }
                    else
                    {
                        throw;
                    }
                }
            }

            if (namespaceswithTimeout.Count > 0)
            {
                var msg =
                    $"Failed to sync all configurations for namespaces:{string.Join(",", namespaceswithTimeout)}."
                    + " Please try again for these namespaces.";

                throw new MetricsClientException(msg);
            }

            return results;
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<IConfigurationUpdateResult>> SyncAllAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrEmpty(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            var operation = $"{this.metricUrlPrefix}/replicateConfigurationToMirrorAccounts";
            var path =
                $"{operation}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricNamespace)}/skipVersionCheck/{skipVersionCheck}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetGlobalEndpoint())
                                 {
                                     Path = path
                                 };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                                   uriBuilder.Uri,
                                   HttpMethod.Post,
                                   this.httpClient,
                                   monitoringAccount.Name,
                                   operation).ConfigureAwait(false);

                return JsonConvert.DeserializeObject<ConfigurationUpdateResult[]>(
                    response.Item1,
                    this.serializerSettings);
            }
            catch (MetricsClientException mce)
            {
                if (mce.ResponseStatusCode == HttpStatusCode.BadRequest)
                {
                    var exMsg = $"Unable to sync all configuration for metric namespace : {metricNamespace} as either "
                        + $"no mirror accounts found for monitoring account : {monitoringAccount.Name} or user doesn't "
                        + $"have permission to update configurations in mirror accounts. Response : {mce.Message}";

                    throw new ConfigurationValidationException(exMsg, ValidationType.ServerSide, mce);
                }

                throw;
            }
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncAllAsyncV2(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var metricReader = new MetricReader(this.connectionInfo);

            var namespaces =
                await metricReader.GetNamespacesAsync(monitoringAccount.Name).ConfigureAwait(false);

            List<ConfigurationUpdateResultList> results = new List<ConfigurationUpdateResultList>();
            foreach (var ns in namespaces)
            {
                var namespaceResults = await this.SyncAllAsyncV2(
                        monitoringAccount,
                        ns,
                        skipVersionCheck).ConfigureAwait(false);

                // For QOS namespaces or other internal namespaces, there is no configuration to
                // replicate and thus the namespace results is an empty list.
                if (namespaceResults.Count > 0)
                {
                    results.AddRange(namespaceResults);
                }
            }

            return results;
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncAllAsyncV2(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrEmpty(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            var metricReader = new MetricReader(this.connectionInfo);
            var metricNames = await metricReader.GetMetricNamesAsync(
                    monitoringAccount.Name,
                    metricNamespace).ConfigureAwait(false);
            var taskList = new List<Task<ConfigurationUpdateResultList>>(this.MaxParallelRunningTasks);

            List<ConfigurationUpdateResultList> results = new List<ConfigurationUpdateResultList>();
            foreach (var metricName in metricNames)
            {
                if (taskList.Count == this.MaxParallelRunningTasks)
                {
                    await this.WaitAllForSyncAllAsyncV2(taskList, results).ConfigureAwait(false);
                    taskList.Clear();
                }

                taskList.Add(this.SyncConfigurationAsync(monitoringAccount, metricNamespace, metricName, skipVersionCheck));
            }

            if (taskList.Count > 0)
            {
                await this.WaitAllForSyncAllAsyncV2(taskList, results).ConfigureAwait(false);
                taskList.Clear();
            }

            return results;
        }

        /// <inheritdoc />
        public async Task<ConfigurationUpdateResultList> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            string metricName,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrWhiteSpace(metricName))
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            var operation = $"{this.metricUrlPrefix}/replicateConfigurationToMirrorAccounts";
            var path =
                $"{operation}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricNamespace)}/skipVersionCheck/{skipVersionCheck}";

            var uriBuilder = new UriBuilder(this.connectionInfo.GetGlobalEndpoint())
            {
                Path = path,
                Query = $"metricName={metricName}"
            };

            var result = new ConfigurationUpdateResultList
            {
                MonitoringAccount = monitoringAccount.Name,
                MetricNamespace = metricNamespace,
                MetricName = metricName
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Post,
                    this.httpClient,
                    monitoringAccount.Name,
                    operation).ConfigureAwait(false);

                result.ConfigurationUpdateResults =
                    JsonConvert.DeserializeObject<ConfigurationUpdateResult[]>(
                        response.Item1,
                        this.serializerSettings);

                foreach (var updateResult in result.ConfigurationUpdateResults)
                {
                    if (!updateResult.Success)
                    {
                        result.Success = false;
                        result.ExceptionMessage = updateResult.Message;
                        return result;
                    }
                }

                // Sync monitor configurations
                var monitorConfigurationUpdatedResult = await this.monitorConfigManager.SyncConfigurationAsync(
                        monitoringAccount,
                        metricNamespace,
                        metricName,
                        skipVersionCheck)
                    .ConfigureAwait(false);

                if (monitorConfigurationUpdatedResult.ConfigurationUpdateResults == null || !monitorConfigurationUpdatedResult.ConfigurationUpdateResults.Any())
                {
                    result.Success = false;
                    return result;
                }

                foreach (var updateResult in monitorConfigurationUpdatedResult.ConfigurationUpdateResults)
                {
                    if (!updateResult.Success)
                    {
                        result.Success = false;
                        result.ExceptionMessage = updateResult.Message;
                        return result;
                    }
                }

                result.Success = true;
                return result;
            }
            catch (MetricsClientException mce)
            {
                result.Success = false;

                if (mce.ResponseStatusCode == HttpStatusCode.Unauthorized || mce.ResponseStatusCode == HttpStatusCode.Forbidden)
                {
                    var exMsg =
                        $"Unable to sync configuration for monitoringAccount:{monitoringAccount.Name}, metricNamespace:"
                        + $"{metricNamespace}, metricName:{metricName} as user"
                        + $"doesn't have permission to update configurations in mirror accounts. Response:{mce.Message}";

                    throw new ConfigurationValidationException(exMsg, ValidationType.ServerSide, mce);
                }
                else
                {
                    result.ExceptionMessage = mce.Message;
                }

                return result;
            }
        }

        /// <inheritdoc />
        public async Task<OperationStatus> DownloadMetricConfigurationAsync(
            string destinationFolder,
            IMonitoringAccount monitoringAccount,
            string metricNamespace = null,
            string metricName = null,
            Regex metricNameRegex = null,
            bool foldersOnNamespacesLevel = false,
            bool downloadDefaultMetricConfig = false,
            int maxFileNameProducedLength = 256)
        {
            const string logTag = "DownloadMetricConfigurationAsync";
            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Target folder to save configurations is {destinationFolder}.");

            if (!FileOperationHelper.CreateFolderIfNotExists(destinationFolder))
            {
                Logger.Log(LoggerLevel.Error, LogId, logTag, $"Cannot create folder {destinationFolder} on local disk.");
                return OperationStatus.FolderCreationError;
            }

            var reader = new MetricReader(this.connectionInfo);
            IReadOnlyList<string> namespaces = string.IsNullOrWhiteSpace(metricNamespace)
                ? await reader.GetNamespacesAsync(monitoringAccount.Name).ConfigureAwait(false)
                : new[] { metricNamespace };

            if (namespaces == null || namespaces.Count == 0)
            {
                Logger.Log(LoggerLevel.Warning, LogId, logTag, $"No namespace is found under {monitoringAccount.Name}!");
                return OperationStatus.ResourceNotFound;
            }

            var operationResult = OperationStatus.CompleteSuccess;

            var getMetricsTaskList = new List<Task<IMetricConfiguration>>();

            // Metrics counters for better statistics
            int totalMetricsCount = 0;
            int retrievedMetricsCount = 0;
            int skippedMetricsCount = 0;

            foreach (var currentNamespace in namespaces)
            {
                var currentFolder = destinationFolder;

                if (foldersOnNamespacesLevel)
                {
                    var subFolder = FileNamePathHelper.ConvertPathToValidFolderName(currentNamespace);
                    currentFolder += Path.DirectorySeparatorChar + subFolder;
                    if (!FileOperationHelper.CreateFolderIfNotExists(currentFolder))
                    {
                        Logger.Log(LoggerLevel.Error, LogId, logTag, $"Cannot create folder {currentFolder} on local disk.");
                        return OperationStatus.FolderCreationError;
                    }
                }

                var metricNames = string.IsNullOrWhiteSpace(metricName)
                    ? await reader.GetMetricNamesAsync(monitoringAccount.Name, currentNamespace).ConfigureAwait(false)
                    : new[] { metricName };

                if (metricNames == null || metricNames.Count == 0)
                {
                    Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"No metric name found under {currentNamespace}.");
                    continue;
                }

                using (var throttler = new SemaphoreSlim(this.MaxParallelRunningTasks))
                {
                    foreach (var currentMetric in metricNames)
                    {
                        if (metricNameRegex != null && !metricNameRegex.IsMatch(currentMetric))
                        {
                            continue;
                        }

                        await throttler.WaitAsync().ConfigureAwait(false);
                        getMetricsTaskList.Add(Task.Run(async () =>
                        {
                            try
                            {
                                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Getting metric {currentMetric} in namespace {currentNamespace} ...");
                                return await this.GetAsync(monitoringAccount, currentNamespace, currentMetric)
                                    .ConfigureAwait(false);
                            }
                            finally
                            {
                                throttler.Release();
                            }
                        }));

                        totalMetricsCount++;
                    }

                    try
                    {
                        await Task.WhenAll(getMetricsTaskList).ConfigureAwait(false);
                    }
                    catch
                    {
                        var getExceptionsTaskList = getMetricsTaskList.Where(t => t.Exception != null);
                        foreach (var exTask in getExceptionsTaskList)
                        {
                            Logger.Log(LoggerLevel.Error, LogId, logTag, $"GetMetricsTasks Exception thrown : {exTask.Exception.Flatten()}");
                        }

                        operationResult = OperationStatus.ResourceGetError;
                    }
                }

                foreach (var successTask in getMetricsTaskList.Where(t => t.Exception == null))
                {
                    // Calculate count of successfully retrieved metrics for processing
                    retrievedMetricsCount++;

                    var downloadedMetric = successTask.Result;
                    var processResult = this.ProcessRetrievedMetrics(
                        downloadedMetric,
                        monitoringAccount.Name,
                        downloadDefaultMetricConfig,
                        maxFileNameProducedLength,
                        currentFolder);

                    // Update current non-error result if any error results returned
                    if ((operationResult == OperationStatus.CompleteSuccess || operationResult == OperationStatus.ResourceSkipped)
                        && processResult != OperationStatus.CompleteSuccess)
                    {
                        operationResult = processResult;
                    }

                    if (processResult == OperationStatus.ResourceSkipped)
                    {
                        skippedMetricsCount++;
                    }
                }

                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Metrics under namespace {currentNamespace} are processed.");

                getMetricsTaskList.Clear();
            }

            Logger.Log(
                LoggerLevel.CustomerFacingInfo,
                LogId,
                logTag,
                $"Detail statistics : For account {monitoringAccount.Name} , totally {totalMetricsCount} metrics are requested, {retrievedMetricsCount} metrics configuration are retrieved, {retrievedMetricsCount - skippedMetricsCount} metrics are saved as files.");

            if (totalMetricsCount == 0 || retrievedMetricsCount == skippedMetricsCount)
            {
                return OperationStatus.ResourceNotFound;
            }

            return operationResult;
        }

        /// <inheritdoc />
        public async Task<OperationStatus> ReplaceAccountNameInMetricConfigurationFilesAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            string replaceAccountNameWith,
            Regex metricNameRegex = null)
        {
            return await this.ModifyMetricConfigurationFilesAsync(
                sourceFolder,
                monitoringAccount,
                metricNameRegex,
                replaceAccountNameWith,
                replaceNamespaceWith: null).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public async Task<OperationStatus> ReplaceNamespaceInMetricConfigurationFilesAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            string replaceNamespaceWith,
            Regex metricNameRegex = null)
        {
            return await this.ModifyMetricConfigurationFilesAsync(
                sourceFolder,
                monitoringAccount,
                metricNameRegex,
                replaceAccountNameWith: null,
                replaceNamespaceWith: replaceNamespaceWith).ConfigureAwait(false);
        }

        /// <inheritdoc />
        public async Task<OperationStatus> UploadMetricConfigurationAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            bool force = false)
        {
            const string logTag = "UploadMetricConfigurationAsync";
            const string jsonFileExtension = "*.json";
            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Folder to read is {sourceFolder}");

            var operationResult = OperationStatus.CompleteSuccess;
            var uploadTaskList = new List<Task>();

            var totalFilesCount = 0;
            var failedFilesCount = 0;

            if (!force)
            {
                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, "Version check is enabled, Server will increment uploaded metric configuration version by 1.");
            }

            using (var throttler = new SemaphoreSlim(this.MaxParallelRunningTasks))
            {
                foreach (var currentConfigFile in Directory.EnumerateFiles(sourceFolder, jsonFileExtension))
                {
                    IMetricConfiguration metricConfigFromFile;
                    try
                    {
                        metricConfigFromFile = this.ReadFileAsMetricConfiguration(currentConfigFile);
                        if (!ConfigFileValidator.ValidateMetricConfigFromFile(metricConfigFromFile))
                        {
                            Logger.Log(LoggerLevel.Error, LogId, logTag, $"Metric Config file {currentConfigFile} failed validation.");
                            operationResult = OperationStatus.FileCorrupted;
                            continue;
                        }
                    }
                    catch (Exception)
                    {
                        operationResult = OperationStatus.FileCorrupted;
                        continue;
                    }

                    totalFilesCount++;

                    await throttler.WaitAsync().ConfigureAwait(false);
                    uploadTaskList.Add(Task.Run(async () =>
                    {
                        try
                        {
                            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Uploading metric configuration from config file {currentConfigFile}");
                            await this.SaveAsync(monitoringAccount, metricConfigFromFile, force);
                        }
                        finally
                        {
                            throttler.Release();
                        }
                    }));
                }

                try
                {
                    await Task.WhenAll(uploadTaskList).ConfigureAwait(false);
                }
                catch
                {
                    var exceptionTaskList = uploadTaskList.Where(t => t.Exception != null);
                    foreach (var exTask in exceptionTaskList)
                    {
                        Logger.Log(LoggerLevel.Error, LogId, logTag, $"Upload Task throw Exception : {exTask.Exception.Flatten()}");
                        failedFilesCount++;
                    }

                    operationResult = OperationStatus.ResourcePostError;
                }
            }

            Logger.Log(
                LoggerLevel.CustomerFacingInfo,
                LogId,
                logTag,
                $"Detail statistics : Total {totalFilesCount} config files are correctly parsed and pending for upload. {totalFilesCount - failedFilesCount} configs are uploaded successfully.");
            return operationResult;
        }

        /// <inheritdoc />
        public async Task<OperationStatus> ApplyTemplateMetricConfigurationAsync(
            string templateFilePath,
            IMonitoringAccount monitoringAccount,
            string metricNamespace = null,
            string metricName = null,
            Regex metricNameRegex = null,
            bool force = false,
            bool whatIf = false)
        {
            const string logTag = "ApplyTemplateMetricConfigurationAsync";
            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"The template file path is {templateFilePath}.");

            IMetricConfiguration metricConfigTemplate;
            try
            {
                metricConfigTemplate = this.ReadFileAsMetricConfiguration(templateFilePath);
                if (!ConfigFileValidator.ValidateMetricConfigFromFile(metricConfigTemplate))
                {
                    Logger.Log(LoggerLevel.Error, LogId, logTag, $"Template file {templateFilePath} failed validation.");
                    return OperationStatus.FileCorrupted;
                }
            }
            catch (Exception ex)
            {
                Logger.Log(LoggerLevel.Error, LogId, logTag, $"Template file {templateFilePath} is corrupted for parsing. Exception: {ex}");
                return OperationStatus.FileCorrupted;
            }

            if (whatIf)
            {
                var templateString = JsonConvert.SerializeObject(metricConfigTemplate, Formatting.Indented, this.serializerSettings);
                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"The template configuration to apply is :\n{templateString}");
                return OperationStatus.CompleteSuccess;
            }

            var reader = new MetricReader(this.connectionInfo);
            IReadOnlyList<string> namespaces = string.IsNullOrWhiteSpace(metricNamespace)
                ? await reader.GetNamespacesAsync(monitoringAccount.Name).ConfigureAwait(false)
                : new[] { metricNamespace };

            if (namespaces == null || namespaces.Count == 0)
            {
                Logger.Log(LoggerLevel.Warning, LogId, logTag, $"No namespace is found under {monitoringAccount.Name}!");
                return OperationStatus.ResourceNotFound;
            }

            var operationResult = OperationStatus.CompleteSuccess;
            var uploadTaskList = new List<Task>();

            var totalMetricsCount = 0;
            var skippedMetricsCount = 0;
            var failedMetricsCount = 0;
            var metricsToApplyCount = 0;

            using (var throttler = new SemaphoreSlim(this.MaxParallelRunningTasks))
            {
                foreach (var currentNamespace in namespaces)
                {
                    var metricNames = string.IsNullOrWhiteSpace(metricName)
                        ? await reader.GetMetricNamesAsync(monitoringAccount.Name, currentNamespace).ConfigureAwait(false)
                        : new[] { metricName };

                    if (metricNames == null || metricNames.Count == 0)
                    {
                        Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"No metric name found under {currentNamespace}.");
                        continue;
                    }

                    foreach (var currentMetric in metricNames)
                    {
                        if (metricNameRegex != null && !metricNameRegex.IsMatch(currentMetric))
                        {
                            continue;
                        }

                        totalMetricsCount++;
                        if (!force)
                        {
                            try
                            {
                                var metricOnServer = await this.GetAsync(monitoringAccount, metricNamespace, metricName).ConfigureAwait(false);
                                bool isDefaultMetric = this.IsDefaultMetric(metricOnServer);

                                // Apply template will not overwrite existing configuration on server unless force option is enabled.
                                if (!isDefaultMetric)
                                {
                                    Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, "Existing metric configuration is detected on server. Skip applying template.");
                                    skippedMetricsCount++;
                                    continue;
                                }
                            }
                            catch (Exception ex)
                            {
                                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Exception getting metric configuration from server. Skip applying template. Exceptions: {ex}");
                                continue;
                            }
                        }

                        var metricConfigToUpload = this.ApplyTemplateConfigWithDifferentMetric(metricConfigTemplate, currentNamespace, currentMetric);

                        await throttler.WaitAsync().ConfigureAwait(false);
                        uploadTaskList.Add(Task.Run(async () =>
                        {
                            try
                            {
                                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Uploading template metric configuration to Metric [{monitoringAccount.Name}][{currentNamespace}][{currentMetric}]");
                                await this.SaveAsync(monitoringAccount, metricConfigToUpload, force);
                            }
                            finally
                            {
                                throttler.Release();
                            }
                        }));
                    }
                }

                try
                {
                    metricsToApplyCount = uploadTaskList.Count;
                    await Task.WhenAll(uploadTaskList).ConfigureAwait(false);
                }
                catch
                {
                    var exceptionTaskList = uploadTaskList.Where(t => t.Exception != null);
                    foreach (var exTask in exceptionTaskList)
                    {
                        Logger.Log(LoggerLevel.Error, LogId, logTag, $"Apply Template upload task throw Exception : {exTask.Exception.Flatten()}");
                        failedMetricsCount++;
                    }

                    operationResult = OperationStatus.ResourcePostError;
                }
            }

            Logger.Log(
                LoggerLevel.CustomerFacingInfo,
                LogId,
                logTag,
                $"Detail statistics : {metricsToApplyCount - failedMetricsCount} metrics are successfully applied.\nTotal {totalMetricsCount} matching metrics are requested for applying template. {skippedMetricsCount} metrics are skipped due to already existence on server.");
            if (totalMetricsCount == 0 || skippedMetricsCount == totalMetricsCount)
            {
                return OperationStatus.ResourceNotFound;
            }

            return operationResult;
        }

        /// <inheritdoc />
        public async Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncAllMetricsAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace = null,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            var metricReader = new MetricReader(this.connectionInfo);
            IReadOnlyList<string> namespaces;
            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                namespaces =
                    await metricReader.GetNamespacesAsync(monitoringAccount.Name).ConfigureAwait(false);
            }
            else
            {
                namespaces = new List<string> { metricNamespace };
            }

            var taskList = new List<Task<ConfigurationUpdateResultList>>(this.MaxParallelRunningTasks);
            var results = new List<ConfigurationUpdateResultList>();
            foreach (var ns in namespaces)
            {
                IReadOnlyList<string> metricNames =
                    await metricReader.GetMetricNamesAsync(
                        monitoringAccount.Name,
                        ns).ConfigureAwait(false);

                foreach (var metricName in metricNames)
                {
                    taskList.Add(
                        this.SyncMetricConfigurationAsync(
                            monitoringAccount,
                            ns,
                            metricName,
                            skipVersionCheck));

                    if (taskList.Count == this.MaxParallelRunningTasks)
                    {
                        await this.WaitAllForSyncAllAsyncV2(taskList, results).ConfigureAwait(false);
                        taskList.Clear();
                    }
                }
            }

            if (taskList.Count > 0)
            {
                await this.WaitAllForSyncAllAsyncV2(taskList, results).ConfigureAwait(false);
                taskList.Clear();
            }

            return results;
        }

        /// <inheritdoc />
        public async Task<ConfigurationUpdateResultList> SyncMetricConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            string metricName,
            bool skipVersionCheck = false)
        {
            if (monitoringAccount == null)
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrWhiteSpace(metricName))
            {
                throw new ArgumentNullException(nameof(metricName));
            }

            var operation = $"{this.metricUrlPrefix}/replicateMetricConfigurationToMirrorAccounts";
            var path =
                $"{operation}/monitoringAccount/{monitoringAccount.Name}/metricNamespace/{SpecialCharsHelper.EscapeTwice(metricNamespace)}";
            var uriBuilder = new UriBuilder(this.connectionInfo.GetGlobalEndpoint())
            {
                Path = path,
                Query = $"metricName={metricName}&skipVersionCheck={skipVersionCheck}"
            };

            var result = new ConfigurationUpdateResultList
            {
                MonitoringAccount = monitoringAccount.Name,
                MetricNamespace = metricNamespace,
                MetricName = metricName
            };

            try
            {
                var response = await HttpClientHelper.GetResponse(
                    uriBuilder.Uri,
                    HttpMethod.Post,
                    this.httpClient,
                    monitoringAccount.Name,
                    operation).ConfigureAwait(false);

                result.ConfigurationUpdateResults =
                    JsonConvert.DeserializeObject<ConfigurationUpdateResult[]>(
                        response.Item1,
                        this.serializerSettings);

                foreach (var updateResult in result.ConfigurationUpdateResults)
                {
                    if (!updateResult.Success)
                    {
                        result.Success = false;
                        result.ExceptionMessage = updateResult.Message;
                        return result;
                    }
                }

                result.Success = true;
                return result;
            }
            catch (MetricsClientException mce)
            {
                result.Success = false;

                if (mce.ResponseStatusCode == HttpStatusCode.Unauthorized)
                {
                    var exMsg =
                        $"Unable to sync configuration for monitoringAccount:{monitoringAccount.Name}, metricNamespace:"
                        + $"{metricNamespace}, metricName:{metricName} as user"
                        + $"doesn't have permission to update configurations in mirror accounts. Response:{mce.Message}";

                    throw new ConfigurationValidationException(exMsg, ValidationType.ServerSide, mce);
                }
                else
                {
                    result.ExceptionMessage = mce.Message;
                }

                return result;
            }
        }

        /// <summary>
        /// Modify local metric configuration json files by replacing AccountName.
        /// </summary>
        /// <param name="sourceFolder">folder in which config files locate.</param>
        /// <param name="monitoringAccount">Monitoring account</param>
        /// <param name="metricNameRegex">metric name regex.</param>
        /// <param name="replaceAccountNameWith">account name to replace original account with in local config files.</param>
        /// <param name="replaceNamespaceWith">namespace to replace original namespace with in local config files.</param>
        /// <returns> OperationStatus.</returns>
        private async Task<OperationStatus> ModifyMetricConfigurationFilesAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            Regex metricNameRegex = null,
            string replaceAccountNameWith = null,
            string replaceNamespaceWith = null)
        {
            const string logTag = "ModifyMetricConfigurationFiles";
            const string jsonFileExtension = "*.json";
            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Folder to read is {sourceFolder}");

            var operationResult = OperationStatus.CompleteSuccess;
            var modifyConfigTaskList = new List<Task>();

            var totalFilesCount = 0;
            var failedFilesCount = 0;

            foreach (var currentConfigFile in Directory.EnumerateFiles(sourceFolder, jsonFileExtension))
            {
                IMetricConfiguration metricConfigFromFile;
                try
                {
                    metricConfigFromFile = this.ReadFileAsMetricConfiguration(currentConfigFile);
                    if (metricNameRegex != null && !metricNameRegex.IsMatch(metricConfigFromFile.Name))
                    {
                        continue;
                    }
                }
                catch (Exception)
                {
                    operationResult = OperationStatus.FileCorrupted;
                    continue;
                }

                totalFilesCount++;
                modifyConfigTaskList.Add(
                    this.ModifyMetricConfigurationAsync(
                        currentConfigFile,
                        metricConfigFromFile,
                        monitoringAccount,
                        replaceAccountNameWith,
                        replaceNamespaceWith));
            }

            try
            {
                await Task.WhenAll(modifyConfigTaskList).ConfigureAwait(false);
            }
            catch
            {
                var modifyExceptionsTaskList = modifyConfigTaskList.Where(t => t.Exception != null);
                foreach (var exTask in modifyExceptionsTaskList)
                {
                    Logger.Log(LoggerLevel.Error, LogId, logTag, $"modifyConfigTasks Exceptions thrown : {exTask.Exception.Flatten()}");
                    failedFilesCount++;
                }

                operationResult = OperationStatus.FileSaveError;
            }

            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Detail statistics : Total {totalFilesCount} config files are correctly read and processed. {totalFilesCount - failedFilesCount} config files are modified successfully.");
            return operationResult;
        }

        /// <summary>
        /// Read a metric config file and deserialize as IMetricConfiguration.
        /// </summary>
        /// <param name="filePath">metric config file to read</param>
        /// <returns>object of IMetricConfiguration</returns>
        private IMetricConfiguration ReadFileAsMetricConfiguration(string filePath)
        {
            const string logTag = "ReadFileAsMetricConfiguration";
            string content = File.ReadAllText(filePath);
            IMetricConfiguration metricConfigurationFromFile;

            try
            {
                metricConfigurationFromFile =
                    JsonConvert.DeserializeObject<RawMetricConfiguration>(content, this.serializerSettings);
                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Processing file {filePath}, this is raw metric.");
            }
            catch
            {
                try
                {
                    metricConfigurationFromFile =
                        JsonConvert.DeserializeObject<CompositeMetricConfiguration>(content, this.serializerSettings);
                    Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Processing file {filePath}, this is composite metric.");
                }
                catch (JsonSerializationException jex)
                {
                    Logger.Log(LoggerLevel.Error, LogId, logTag, $"Cannot deserialize Json file {filePath}. Exceptions : {jex}");
                    throw;
                }
            }

            return metricConfigurationFromFile;
        }

        /// <summary>
        /// Process sucessfully retrived metric configuration objects.
        /// </summary>
        /// <param name="downloadedMetric">metric configuration</param>
        /// <param name="accountName">account name</param>
        /// <param name="downloadDefaultMetricConfig">whether save default retrieved metric configuration</param>
        /// <param name="maxFileNameProducedLength">max size of file name length</param>
        /// <param name="curFolder">folder to store this metric configuration</param>
        /// <returns>OperationStatus</returns>
        private OperationStatus ProcessRetrievedMetrics(
            IMetricConfiguration downloadedMetric,
            string accountName,
            bool downloadDefaultMetricConfig,
            int maxFileNameProducedLength,
            string curFolder)
        {
            const string logTag = "ProcessRetrievedMetrics";
            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Processing retrieved metric {downloadedMetric.Name} configuration.");

            if (!downloadDefaultMetricConfig)
            {
                if (downloadedMetric.LastUpdatedTime == default(DateTime))
                {
                    Logger.Log(
                        LoggerLevel.CustomerFacingInfo,
                        LogId,
                        logTag,
                        $"Skipping default metric config for metric {downloadedMetric.Name} in namespace {downloadedMetric.MetricNamespace}");
                    return OperationStatus.ResourceSkipped;
                }
            }

            var fileName = FileNamePathHelper.ConstructValidFileName(
                accountName,
                downloadedMetric.MetricNamespace,
                downloadedMetric.Name,
                string.Empty,
                FileNamePathHelper.JsonFileExtension,
                maxFileNameProducedLength);

            Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Saving metric config file {fileName} ...");
            try
            {
                FileOperationHelper.SaveContentToFile(
                    Path.Combine(curFolder, fileName),
                    JsonConvert.SerializeObject(downloadedMetric, Formatting.Indented, this.serializerSettings));

                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Saved metric config file {fileName}.");
                return OperationStatus.CompleteSuccess;
            }
            catch (Exception ex)
            {
                Logger.Log(LoggerLevel.Error, LogId, logTag, $"Failed writing file {fileName}. Exception: {ex}");
                return OperationStatus.FileSaveError;
            }
        }

        /// <summary>
        /// Async modify IMetricConfiguration and save to file
        /// </summary>
        /// <param name="configFile">configuration file to save modified config</param>
        /// <param name="metricConfigFromFile">original IMetricConfiguration read from file</param>
        /// <param name="monitoringAccount">MonitoringAccount</param>
        /// <param name="replaceAccountNameWith">account name to replace original account with in local config files</param>
        /// <param name="replaceNamespaceWith">namespace to replace original namespace with in local config files</param>
        /// <returns>return a Task</returns>
        private async Task ModifyMetricConfigurationAsync(
            string configFile,
            IMetricConfiguration metricConfigFromFile,
            IMonitoringAccount monitoringAccount,
            string replaceAccountNameWith,
            string replaceNamespaceWith)
        {
            const string logTag = "ModifyMetricConfigurationAsync";
            IMetricConfiguration newMetricConfig;

            if (metricConfigFromFile is RawMetricConfiguration)
            {
                newMetricConfig = this.CopyAndReplaceRawMetricConfig(
                    (RawMetricConfiguration)metricConfigFromFile,
                    replaceAccountNameWith,
                    replaceNamespaceWith);
            }
            else
            {
                newMetricConfig = this.CopyAndReplaceCompositeMetricConfig(
                    (CompositeMetricConfiguration)metricConfigFromFile,
                    monitoringAccount,
                    replaceAccountNameWith,
                    replaceNamespaceWith);
            }

            try
            {
                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, logTag, $"Writing file {configFile}...");

                await FileOperationHelper.SaveContentToFileAsync(
                    configFile,
                    JsonConvert.SerializeObject(newMetricConfig, Formatting.Indented, this.serializerSettings)).ConfigureAwait(false);
            }
            catch
            {
                Logger.Log(LoggerLevel.Error, LogId, logTag, $"Exceptions in modifying local file {configFile}.");
                throw;
            }
        }

        /// <summary>
        /// Copy original RawMetricConfiguration and do modification.
        /// </summary>
        /// <param name="rawMetricConfigFromFile">original RawMetricConfiguration from file</param>
        /// <param name="replaceAccountNameWith">account name to replace original account with in local config files</param>
        /// <param name="replaceNamespaceWith">namespace to replace original namespace with in local config files</param>
        /// <returns>modified RawMetricConfiguration</returns>
        private RawMetricConfiguration CopyAndReplaceRawMetricConfig(
            RawMetricConfiguration rawMetricConfigFromFile,
            string replaceAccountNameWith,
            string replaceNamespaceWith)
        {
            if (!string.IsNullOrWhiteSpace(replaceAccountNameWith))
            {
                Logger.Log(LoggerLevel.CustomerFacingInfo, LogId, "CopyAndReplaceRawMetricConfig", $"Metric {rawMetricConfigFromFile.Name} is RawMetricConfiguration and ReplaceAccountNameWith is not applicable.");
            }

            var newMetricNamespace = string.IsNullOrWhiteSpace(replaceNamespaceWith)
                ? rawMetricConfigFromFile.MetricNamespace
                : replaceNamespaceWith;

            return new RawMetricConfiguration(
                newMetricNamespace,
                rawMetricConfigFromFile.Name,
                rawMetricConfigFromFile.LastUpdatedTime,
                rawMetricConfigFromFile.LastUpdatedBy,
                rawMetricConfigFromFile.Version,
                rawMetricConfigFromFile.ScalingFactor,
                rawMetricConfigFromFile.EnableClientPublication,
                rawMetricConfigFromFile.EnableClientForking,
                rawMetricConfigFromFile.Description,
                rawMetricConfigFromFile.Dimensions,
                rawMetricConfigFromFile.Preaggregations,
                rawMetricConfigFromFile.RawSamplingTypes,
                rawMetricConfigFromFile.ComputedSamplingTypes,
                rawMetricConfigFromFile.EnableClientSideLastSamplingMode,
                rawMetricConfigFromFile.EnableClientEtwPublication);
        }

        /// <summary>
        /// Copy original CompositeMetricConfiguration and do modification.
        /// </summary>
        /// <param name="compositeMetricConfigFromFile">original CompositeMetricConfiguration from file</param>
        /// <param name="monitoringAccount">MonitoringAccount</param>
        /// <param name="replaceAccountNameWith">account name to replace original account with in local config files</param>
        /// <param name="replaceNamespaceWith">namespace to replace original namespace with in local config files</param>
        /// <returns>modified CompositeMetricConfiguration</returns>
        private CompositeMetricConfiguration CopyAndReplaceCompositeMetricConfig(
            CompositeMetricConfiguration compositeMetricConfigFromFile,
            IMonitoringAccount monitoringAccount,
            string replaceAccountNameWith,
            string replaceNamespaceWith)
        {
            var newAccountName = string.IsNullOrWhiteSpace(replaceAccountNameWith)
                ? monitoringAccount.Name
                : replaceAccountNameWith;

            var newMetricNamespace = string.IsNullOrWhiteSpace(replaceNamespaceWith)
                ? compositeMetricConfigFromFile.MetricNamespace
                : replaceNamespaceWith;

            // deep copy metric sources by replacing matching account name and namespace at same time
            var newMetricSources = new List<CompositeMetricSource>();
            foreach (var source in compositeMetricConfigFromFile.MetricSources)
            {
                var newSourceAccountName =
                    source.MonitoringAccount.Equals(monitoringAccount.Name, StringComparison.OrdinalIgnoreCase)
                        ? newAccountName
                        : source.MonitoringAccount;
                var newSourceNamespace =
                    source.MetricNamespace.Equals(compositeMetricConfigFromFile.MetricNamespace, StringComparison.OrdinalIgnoreCase)
                        ? newMetricNamespace
                        : source.MetricNamespace;

                newMetricSources.Add(new CompositeMetricSource(source.DisplayName, newSourceAccountName, newSourceNamespace, source.Metric));
            }

            var newCompositeMetricConfig = new CompositeMetricConfiguration(
                newMetricNamespace,
                compositeMetricConfigFromFile.Name,
                compositeMetricConfigFromFile.LastUpdatedTime,
                compositeMetricConfigFromFile.LastUpdatedBy,
                compositeMetricConfigFromFile.Version,
                compositeMetricConfigFromFile.TreatMissingSeriesAsZeroes,
                compositeMetricConfigFromFile.Description,
                newMetricSources,
                compositeMetricConfigFromFile.CompositeExpressions);

            return newCompositeMetricConfig;
        }

        /// <summary>
        /// Apply template IMetricConfiguration object by replacing MetricNamespace and Name.
        /// </summary>
        /// <param name="metricConfigTemplate">template IMetricConfiguration read from file</param>
        /// <param name="targetNamespace">target MetricNamespace</param>
        /// <param name="targetMetricName">target metric Name</param>
        /// <returns>IMetricConfiguration</returns>
        private IMetricConfiguration ApplyTemplateConfigWithDifferentMetric(
            IMetricConfiguration metricConfigTemplate,
            string targetNamespace,
            string targetMetricName)
        {
            IMetricConfiguration newMetricConfig;

            if (metricConfigTemplate is RawMetricConfiguration)
            {
                var rawMetricConfigTemplate = (RawMetricConfiguration)metricConfigTemplate;
                newMetricConfig = new RawMetricConfiguration(
                    targetNamespace,
                    targetMetricName,
                    rawMetricConfigTemplate.LastUpdatedTime,
                    rawMetricConfigTemplate.LastUpdatedBy,
                    rawMetricConfigTemplate.Version,
                    rawMetricConfigTemplate.ScalingFactor,
                    rawMetricConfigTemplate.EnableClientPublication,
                    rawMetricConfigTemplate.EnableClientForking,
                    rawMetricConfigTemplate.Description,
                    rawMetricConfigTemplate.Dimensions,
                    rawMetricConfigTemplate.Preaggregations,
                    rawMetricConfigTemplate.RawSamplingTypes,
                    rawMetricConfigTemplate.ComputedSamplingTypes,
                    rawMetricConfigTemplate.EnableClientSideLastSamplingMode,
                    rawMetricConfigTemplate.EnableClientEtwPublication);
            }
            else
            {
                var compositeMetricConfigTemplate = (CompositeMetricConfiguration)metricConfigTemplate;
                newMetricConfig = new CompositeMetricConfiguration(
                    targetNamespace,
                    targetMetricName,
                    compositeMetricConfigTemplate.LastUpdatedTime,
                    compositeMetricConfigTemplate.LastUpdatedBy,
                    compositeMetricConfigTemplate.Version,
                    compositeMetricConfigTemplate.TreatMissingSeriesAsZeroes,
                    compositeMetricConfigTemplate.Description,
                    compositeMetricConfigTemplate.MetricSources,
                    compositeMetricConfigTemplate.CompositeExpressions);
            }

            return newMetricConfig;
        }

        /// <summary>
        /// true if this is a default configuration.
        /// </summary>
        /// <param name="metricConfiguration">Monitoring Account</param>
        /// <returns>true if default configuration returned.</returns>
        private bool IsDefaultMetric(IMetricConfiguration metricConfiguration)
        {
            const string logTag = "IsDefaultMetricAsync";
            if (metricConfiguration == null)
            {
                Logger.Log(LoggerLevel.Error, LogId, logTag, $"Argument MetricConfiguration [{metricConfiguration.MetricNamespace}][{metricConfiguration.Name}] is NULL.");
                throw new ArgumentNullException(nameof(metricConfiguration));
            }

            return metricConfiguration.LastUpdatedTime == default(DateTime);
        }

        /// <summary>
        /// A helper method which waits for all given sync all tasks to complete.
        /// </summary>
        /// <param name="taskList">The task list.</param>
        /// <param name="results">The results.</param>
        /// <returns>An awaitable <see cref="Task"/>.</returns>
        private async Task WaitAllForSyncAllAsyncV2(
            List<Task<ConfigurationUpdateResultList>> taskList,
            List<ConfigurationUpdateResultList> results)
        {
            const string logTag = "SyncAllAsyncV2";
            try
            {
                await Task.WhenAll(taskList).ConfigureAwait(false);
                foreach (var task in taskList)
                {
                    if (task.Result.Success)
                    {
                        // For metric configuration in QOS or other internal namespace, there is no configuration
                        // to replicate and thus the updates results is an empty list.
                        if (task.Result.ConfigurationUpdateResults.Count > 0)
                        {
                            results.Add(task.Result);
                        }
                    }
                    else
                    {
                        if (task.Result.ExceptionMessage.Contains(
                            "Event configuration to be updated can't be null."))
                        {
                            // No configuration exist for the specified metrics and hence nothing
                            // to replicate.
                            continue;
                        }

                        results.Add(task.Result);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Log(
                    LoggerLevel.Error,
                    LogId,
                    logTag,
                    $"Exception occured while replicating configuration. Exception: {ex}");
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricFilterDetails.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Configuration
{
    /// <summary>
    /// The details of the filter in order to filter the hints.
    /// </summary>
    public sealed class MetricFilterDetails
    {
        /// <summary>
        /// The prefix to be used before a sampling type in the <see cref="Expression"/>.
        /// </summary>
        public const string Prefix = "raw";

        /// <summary>
        /// The expression to be applied on each and every minutely data within the specified time interval.
        /// Example: <see cref="Prefix"/>.Sum &gt; 100 && <see cref="Prefix"/>.Count &lt; 2
        /// </summary>
        /// <remarks>
        /// The filter expression in <see cref="MetricFilterDetails.Expression"/> is applied on minutely values
        /// and the value of the dimension is returned even if the one of the minutely data returns true for the expression provided.
        /// </remarks>
        public string Expression { get; set; }

        /// <summary>
        /// Validates the contents of this instance.
        /// </summary>
        /// <returns>True if the filter is valid otherwise false.</returns>
        public bool IsValid()
        {
            return this.Expression != null && !string.IsNullOrEmpty(this.Expression.Trim());
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FilteredTimeSeriesQueryRequest.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using System.Collections.Generic;
    using Metrics;
    using Newtonsoft.Json;
    using Online.Metrics.Serialization.Configuration;

    /// <summary>
    /// Request to query data with dimensional filters and at Top N condition and retrieve the
    /// resultant series.
    /// </summary>
    public sealed class FilteredTimeSeriesQueryRequest
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeriesQueryRequest" /> class.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="seriesResolutionInMinutes">The series resolution in minutes.</param>
        /// <param name="aggregationType">Type of the aggregation.</param>
        /// <param name="topPropertyDefinition">The top property definition.</param>
        /// <param name="numberOfResultsToReturn">The number of results to return.</param>
        /// <param name="orderBy">The order by.</param>
        /// <param name="zeroAsNoValueSentinel">Indicates whether zero should be used as no value sentinel, or double.NaN.</param>
        /// <param name="outputDimensionNames">The dimension names to be used for the result time series.  If not set, same as the dimensions in the dimension filter.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.</param>
        public FilteredTimeSeriesQueryRequest(
            MetricIdentifier metricIdentifier,
            IReadOnlyList<SamplingType> samplingTypes,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            int seriesResolutionInMinutes,
            AggregationType aggregationType,
            PropertyDefinition topPropertyDefinition,
            int numberOfResultsToReturn,
            OrderBy orderBy,
            bool zeroAsNoValueSentinel,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false)
            : this(
                metricIdentifier,
                null,
                null,
                null,
                samplingTypes,
                dimensionFilters,
                startTimeUtc,
                endTimeUtc,
                seriesResolutionInMinutes,
                aggregationType,
                topPropertyDefinition,
                numberOfResultsToReturn,
                orderBy,
                zeroAsNoValueSentinel,
                false,
                outputDimensionNames,
                lastValueMode)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeriesQueryRequest" /> class.
        /// </summary>
        /// <param name="monitoringAccountNames">The monitoring account names.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="seriesResolutionInMinutes">The series resolution in minutes.</param>
        /// <param name="aggregationType">Type of the aggregation.</param>
        /// <param name="topPropertyDefinition">The top property definition.</param>
        /// <param name="numberOfResultsToReturn">The number of results to return.</param>
        /// <param name="orderBy">The order by.</param>
        /// <param name="zeroAsNoValueSentinel">Indicates whether zero should be used as no value sentinel, or double.NaN.</param>
        /// <param name="aggregateAcrossAccounts">if set to <c>true</c>, aggregate data across accounts, and the account name in the query results is "AccountMoniker".</param>
        /// <param name="outputDimensionNames">The dimension names to be used for the result time series.  If not set, same as the dimensions in the dimension filter.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode.</param>
        public FilteredTimeSeriesQueryRequest(
            IReadOnlyList<string> monitoringAccountNames,
            string metricNamespace,
            string metricName,
            IReadOnlyList<SamplingType> samplingTypes,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            int seriesResolutionInMinutes,
            AggregationType aggregationType,
            PropertyDefinition topPropertyDefinition,
            int numberOfResultsToReturn,
            OrderBy orderBy,
            bool zeroAsNoValueSentinel,
            bool aggregateAcrossAccounts,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false)
            : this(
                default(MetricIdentifier),
                monitoringAccountNames,
                metricNamespace,
                metricName,
                samplingTypes,
                dimensionFilters,
                startTimeUtc,
                endTimeUtc,
                seriesResolutionInMinutes,
                aggregationType,
                topPropertyDefinition,
                numberOfResultsToReturn,
                orderBy,
                zeroAsNoValueSentinel,
                aggregateAcrossAccounts,
                outputDimensionNames,
                lastValueMode)
        {
            if (monitoringAccountNames == null || monitoringAccountNames.Count == 0)
            {
                throw new ArgumentException("must not be null or empty", nameof(monitoringAccountNames));
            }

            for (int i = 0; i < monitoringAccountNames.Count; i++)
            {
                if (string.IsNullOrWhiteSpace(monitoringAccountNames[i]))
                {
                    throw new ArgumentException($"All monitoring accounts must not be null or empty: {string.Join(",", monitoringAccountNames)}.", nameof(monitoringAccountNames));
                }
            }

            // Make de-serialization not throw: MetricIdentifier is a struct and in addition it doesn't allow null-or-empty property members.
            this.MetricIdentifier = new MetricIdentifier(monitoringAccountNames[0], metricNamespace, metricName);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeriesQueryRequest"/> class.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <remarks>For OBO, we fill in only the <see cref="MetricIdentifier"/> property on success but we do fill in the full request on failure.</remarks>
        internal FilteredTimeSeriesQueryRequest(MetricIdentifier metricIdentifier)
        {
            this.MetricIdentifier = metricIdentifier;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeriesQueryRequest" /> class.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <param name="monitoringAccountNames">The monitoring account names.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="seriesResolutionInMinutes">The series resolution in minutes.</param>
        /// <param name="aggregationType">Type of the aggregation.</param>
        /// <param name="topPropertyDefinition">The top property definition.</param>
        /// <param name="numberOfResultsToReturn">The number of results to return.</param>
        /// <param name="orderBy">The order by.</param>
        /// <param name="zeroAsNoValueSentinel">Indicates whether zero should be used as no value sentinel, or double.NaN.</param>
        /// <param name="aggregateAcrossAccounts">if set to <c>true</c>, aggregate data across accounts, and the account name in the query results is "AccountMoniker".</param>
        /// <param name="outputDimensionNames">The dimension names to be used for the result time series.  If not set, same as the dimensions in the dimension filter.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.</param>
        [JsonConstructor]
        private FilteredTimeSeriesQueryRequest(
            MetricIdentifier metricIdentifier,
            IReadOnlyList<string> monitoringAccountNames,
            string metricNamespace,
            string metricName,
            IReadOnlyList<SamplingType> samplingTypes,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            int seriesResolutionInMinutes,
            AggregationType aggregationType,
            PropertyDefinition topPropertyDefinition,
            int numberOfResultsToReturn,
            OrderBy orderBy,
            bool zeroAsNoValueSentinel,
            bool aggregateAcrossAccounts,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false)
        {
            this.MetricIdentifier = metricIdentifier;
            this.MonitoringAccountNames = monitoringAccountNames;
            this.MetricNamespace = metricNamespace;
            this.MetricName = metricName;
            this.SamplingTypes = samplingTypes;
            this.DimensionFilters = dimensionFilters;
            this.StartTimeUtc = startTimeUtc;
            this.EndTimeUtc = endTimeUtc;
            this.SeriesResolutionInMinutes = seriesResolutionInMinutes;
            this.AggregationType = aggregationType;
            this.TopPropertyDefinition = topPropertyDefinition;
            this.NumberOfResultsToReturn = numberOfResultsToReturn;
            this.OrderBy = orderBy;
            this.ZeroAsNoValueSentinel = zeroAsNoValueSentinel;
            this.AggregateAcrossAccounts = aggregateAcrossAccounts;
            this.OutputDimensionNames = outputDimensionNames;
            this.LastValueMode = lastValueMode;
        }

        /// <summary>
        /// Gets the monitoring accounts to be queried.
        /// </summary>
        public IReadOnlyList<string> MonitoringAccountNames { get; }

        /// <summary>
        /// Gets the metric namespace.
        /// </summary>
        public string MetricNamespace { get; }

        /// <summary>
        /// Gets the name of the metric.
        /// </summary>
        public string MetricName { get; }

        /// <summary>
        /// Gets the metric identifier to be queried.
        /// </summary>
        public MetricIdentifier MetricIdentifier { get; }

        /// <summary>
        /// Gets the sampling types to be queried.
        /// </summary>
        public IReadOnlyList<SamplingType> SamplingTypes { get; }

        /// <summary>
        /// Gets the dimension filters used to determine which time series keys should be
        /// retrieved from the data store.
        /// </summary>
        public IReadOnlyList<DimensionFilter> DimensionFilters { get; }

        /// <summary>
        /// Gets the start time of the query period in UTC.
        /// </summary>
        public DateTime StartTimeUtc { get; }

        /// <summary>
        /// Gets the end time of the query period in UTC.
        /// </summary>
        public DateTime EndTimeUtc { get; }

        /// <summary>
        /// Gets the series resolution in minutes.  One data point will represent x number
        /// of minutes of raw data.
        /// </summary>
        public int SeriesResolutionInMinutes { get; }

        /// <summary>
        /// Gets the type of the aggregation to be performed when resolution is being reduced.
        /// </summary>
        public AggregationType AggregationType { get; }

        /// <summary>
        /// Gets the top property definition.  This defines which sampling type
        /// </summary>
        public PropertyDefinition TopPropertyDefinition { get; }

        /// <summary>
        /// Gets the number of results to return.
        /// </summary>
        public int NumberOfResultsToReturn { get; }

        /// <summary>
        /// Gets the ordering of the results, either Ascending or Descending.
        /// </summary>
        public OrderBy OrderBy { get; }

        /// <summary>
        /// Indicates if zero or double.NaN should be used to indicate no value in time series data.
        /// </summary>
        public bool ZeroAsNoValueSentinel { get; }

        /// <summary>
        /// Gets a value indicationg whether to aggregate data across accounts.
        /// </summary>
        public bool AggregateAcrossAccounts { get; }

        /// <summary>
        /// Gets or sets the output dimension names to be returned with the result time series.  If the output dimension names are not specified,
        /// The dimensions used as dimension filters will be the output dimensions.
        /// </summary>
        public IReadOnlyList<string> OutputDimensionNames { get; }

        /// <summary>
        /// Indicates if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.
        /// </summary>
        public bool LastValueMode { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TraceEvent.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
//   Type that wraps access to the fields of an ETW event record.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics.Etw
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>
    /// Type that wraps access to the fields of an ETW event record.
    /// </summary>
    internal unsafe struct TraceEvent
    {
        /// <summary>
        /// Pointer to the native structure being wrapped.
        /// </summary>
        [SuppressMessage("Microsoft.Security", "CA2151:FieldsWithCriticalTypesShouldBeCriticalFxCopRule", Justification = "Not accessible to any 3rd-party MS or not")]
        private readonly NativeMethods.EventRecord* eventRecord;

        /// <summary>
        /// Managed type that allows access to the event header.
        /// </summary>
        private readonly TraceEventHeader eventHeader;

        /// <summary>
        /// Managed type that allows access to the buffer context of the event.
        /// </summary>
        private readonly TraceBufferContext bufferContext;

        /// <summary>
        /// Initializes a new instance of the <see cref="TraceEvent"/> struct.
        /// </summary>
        /// <param name="eventRecord">
        /// Pointer to the native structure being wrapped by the instance.
        /// </param>
        public TraceEvent(NativeMethods.EventRecord* eventRecord)
            : this()
        {
            if (eventRecord == null)
            {
                throw new ArgumentNullException("eventRecord");
            }

            this.eventRecord = eventRecord;
            this.eventHeader = new TraceEventHeader(&eventRecord->EventHeader);
            this.bufferContext = new TraceBufferContext(&eventRecord->BufferContext);
        }

        /// <summary>
        /// Gets the header of the event.
        /// </summary>
        public TraceEventHeader Header
        {
            get
            {
                return this.eventHeader;
            }
        }

        /// <summary>
        /// Gets the buffer context of the event.
        /// </summary>
        public TraceBufferContext BufferContext
        {
            get
            {
                return this.bufferContext;
            }
        }

        /// <summary>
        /// Gets the number of extended data structures in ExtendedData.
        /// </summary>
        public ushort ExtendedDataCount
        {
            get
            {
                return this.eventRecord->ExtendedDataCount;
            }
        }

        /// <summary>
        /// Gets the Size, in bytes, of the data in UserData.
        /// </summary>
        public ushort UserDataLength
        {
            get
            {
                return this.eventRecord->UserDataLength;
            }
        }

        /// <summary>
        /// Gets the extended data items that ETW collects if the controller sets the EnableProperty
        /// parameter of EnableTraceEx. For details, see EVENT_HEADER_EXTENDED_DATA_ITEM.
        /// </summary>
        public IntPtr ExtendedData
        {
            get
            {
                return this.eventRecord->ExtendedData;
            }
        }

        /// <summary>
        /// Gets the event specific data. To parse this data, see Retrieving Event Data Using TDH.
        /// If the Flags member of EVENT_HEADER is EVENT_HEADER_FLAG_STRING_ONLY, the data is a
        /// null-terminated Unicode string that you do not need TDH to parse.
        /// </summary>
        public IntPtr UserData
        {
            get
            {
                return this.eventRecord->UserData;
            }
        }

        /// <summary>
        /// Gets the context specified in the Context member of the EVENT_TRACE_LOGFILE structure
        /// that is passed to OpenTrace.
        /// </summary>
        public IntPtr UserContext
        {
            get
            {
                return this.eventRecord->UserContext;
            }
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="IMetricMetadata.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;

    /// <summary>
    /// Represents an interface for the metric metadata object.
    /// </summary>
    public interface IMetricMetadata
    {
        /// <summary>
        /// Gets the namespace of the metric.
        /// </summary>
        string MetricNamespace { get; }

        /// <summary>
        /// Gets the name of the metric.
        /// </summary>
        string MetricName { get; }

        /// <summary>
        /// Gets the number of dimensions the metric has.
        /// </summary>
        int DimensionsCount { get; }

        /// <summary>
        /// Gets the name of the dimension by index.
        /// </summary>
        /// <param name="dimensionIndex">Index of the dimension in 0..DimensionsCount-1 range.</param>
        /// <returns>Name of the dimension.</returns>
        /// <exception cref="IndexOutOfRangeException">Thrown when index is out of the specified range.</exception>
        string GetDimensionName(int dimensionIndex);
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OboMetricReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Query;
    using Microsoft.Cloud.Metrics.Client.Utility;

    /// <summary>
    /// The metrics reader class to read metrics data for OBO V2 - multiple dimension support.
    /// </summary>
    public sealed class OboMetricReader
    {
        private readonly ConnectionInfo connectionInfo;
        private readonly HttpClient httpClient;
        private readonly string clientId;

        /// <summary>
        /// Initializes a new instance of the <see cref="OboMetricReader"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        /// <param name="clientId">The string identifying client.</param>
        public OboMetricReader(ConnectionInfo connectionInfo, string clientId = "OBO")
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            this.connectionInfo = connectionInfo;
            this.clientId = clientId;
            this.httpClient = HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo);
        }

        /// <summary>
        /// Gets the filtered time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="numMinutes">The number minutes.</param>
        /// <param name="resourceId">The resource identifier.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="categories">The categories.</param>
        /// <returns>List of <see cref="IFilteredTimeSeriesQueryResponse"/>.</returns>
        public async Task<IReadOnlyList<IFilteredTimeSeriesQueryResponse>> GetFilteredTimeSeriesAsync(DateTime startTimeUtc, int numMinutes, string resourceId, SamplingType[] samplingTypes, List<string> categories)
        {
            var startMinute = startTimeUtc.ToString("yyyy-MM-ddTHH:mmZ");
            var endpoint = new Uri(this.connectionInfo.Endpoint, $"/api/getMetricsForOBO/v2/serializationVersion/{FilteredTimeSeriesQueryResponse.CurrentVersion}/startMinute/{startMinute}/numMinutes/{numMinutes}");

            var traceId = Guid.NewGuid();
            var httpContent = Tuple.Create(new List<string> { resourceId }, samplingTypes, categories);

            var response = await HttpClientHelper.GetResponse(
                endpoint,
                HttpMethod.Post,
                this.httpClient,
                null, // TODO add support of monitoring account on server side and then pass it here
                null, // TODO add support of monitoring account on server side and pass operation here
                httpContent,
                traceId: traceId,
                clientId: this.clientId,
                numAttempts: 1).ConfigureAwait(false);

            string handlingRpServerId;
            IReadOnlyList<IFilteredTimeSeriesQueryResponse> results;
            using (HttpResponseMessage httpResponseMessage = response.Item2)
            {
                IEnumerable<string> handlingRpServerIdValues;
                httpResponseMessage.Headers.TryGetValues("__HandlingRpServerId__", out handlingRpServerIdValues);
                handlingRpServerId = handlingRpServerIdValues?.FirstOrDefault();

                using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
                {
                    results = FilteredQueryResponseDeserializer.Deserialize(stream);
                }
            }

            foreach (var queryResponse in results)
            {
                queryResponse.DiagnosticInfo.TraceId = traceId.ToString("B");
                queryResponse.DiagnosticInfo.HandlingServerId = handlingRpServerId;
            }

            return results;
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="ILogEngine.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Logging
{
    using System;

    /// <summary>
    /// Level of the log messages.
    /// </summary>
    /// <remarks>
    /// Level comments taken from System.Diagnostics.Tracing.EventLevel
    /// </remarks>
    public enum LoggerLevel
    {
        /// <summary>
        /// This level adds standard errors that signify a problem.
        /// </summary>
        Error = 0,

        /// <summary>
        /// This level adds warning events (for example, events that are published
        /// because a disk is nearing full capacity).
        /// </summary>
        Warning = 1,

        /// <summary>
        /// This level adds only customer-facing infos.
        /// </summary>
        CustomerFacingInfo = 2,

        /// <summary>
        /// This level adds informational events or messages that are not errors.
        /// These events can help trace the progress or state of an application.
        /// </summary>
        Info = 3,

        /// <summary>
        /// This level adds lengthy events or messages.
        /// </summary>
        Debug = 4
    }

    /// <summary>
    /// Interface that needs to be supported by any log engine to be used by the process.
    /// </summary>
    public interface ILogEngine : IDisposable
    {
        /// <summary>
        /// Logs the given data according to the engine implementation.
        /// </summary>
        /// <param name="level">
        /// Level of the log statement.
        /// </param>
        /// <param name="logId">
        /// Log identification for classifying log statements.
        /// </param>
        /// <param name="tag">
        /// Extra string that allows another level of classification under the log id.
        /// </param>
        /// <param name="format">
        /// Message to be logged, it can be a format message.
        /// </param>
        /// <param name="objectParams">
        /// Optional, any parameter to be used to build the formatted message string.
        /// </param>
        void Log(
            LoggerLevel level,
            object logId,
            string tag,
            string format,
            params object[] objectParams);

        /// <summary>
        /// Checks if a log statement with the given parameters will be actually logged or
        /// not. Useful to avoid expensive operations for log statements that are going to
        /// be dropped by the log engine.
        /// </summary>
        /// <param name="level">
        /// Level of the log statement.
        /// </param>
        /// <param name="logId">
        /// Log identification for classifying log statements.
        /// </param>
        /// <param name="tag">
        /// Extra string that allows another level of classification under the log id.
        /// </param>
        /// <returns>
        /// True if the statement is going to be logged, false otherwise.
        /// </returns>
        bool IsLogged(LoggerLevel level, object logId, string tag);
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="MetricSerializer.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;

    /// <summary>
    /// Serializer used for serializing metric data between
    ///     - Metrics extension and FrontEnd
    ///     - Metric puller service and FrontEnd
    ///     - FrontEnd and metrics aggregator for cache server metrics
    /// </summary>
    public sealed class MetricSerializer
    {
        /// <summary>
        /// The maximum version that serializer can produce.
        /// To be used internally. The external code should
        /// always track their supported serializer/deserializer versions separately
        /// and be independent from maximum version supported by MetricSerializer
        /// </summary>
        private const ushort MaxVersion = 6;
        private const uint TypeSerializerFlags = 0x12020000; // Corresponds to 0001.001.0000.0001.00000000000000000 (Use string and metadata interning with variable-length integer serialization)
        private const uint TempBufferSize = 1500;
        private readonly Dictionary<string, uint> stringIndexes = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase);
        private readonly List<string> strings = new List<string>();
        private readonly Dictionary<IMetricMetadata, uint> metadataIndexes = new Dictionary<IMetricMetadata, uint>(MetricEqualityComparer.Instance);
        private readonly List<IMetricMetadata> metadatas = new List<IMetricMetadata>();
        private readonly ushort hllSerializationVersion;
        private readonly bool estimatePacketSize;
        private readonly float stringCharEstimatedSizeInBytes;
        private ushort version;
        private uint nextStringIndex;
        private uint nextMetadataIndex;
        private long currentMetricDataBlockSize;
        private long currentMetadataDictionaryBlockSize;
        private long currentStringDictionaryBlockSize;
        private byte[] tempBuffer;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricSerializer"/> class.
        /// </summary>
        /// <param name="version">Serialization version to use (from 0 to MaxVersion).</param>
        /// <param name="hllSerializationVersion">Hll serialization version till whi</param>
        /// <param name="estimatePacketSize">Flag to know whether to do packet size estimate calculations as data is serialized.</param>
        /// <param name="stringCharEstimatedSizeInBytes">Estimated size in bytes for each character in strings used inside the packet.</param>
        public MetricSerializer(ushort version = 1, ushort hllSerializationVersion = 1, bool estimatePacketSize = false, float stringCharEstimatedSizeInBytes = 1.5f)
        {
            if (version > MaxVersion)
            {
                throw new ArgumentException("Version number is greated than maximum current version supported: " + MaxVersion, nameof(version));
            }

            if (stringCharEstimatedSizeInBytes > 2 || stringCharEstimatedSizeInBytes < 1)
            {
                throw new ArgumentException($"{nameof(stringCharEstimatedSizeInBytes)} cannot be greater than 2 and less than 1");
            }

            this.version = version;
            this.hllSerializationVersion = hllSerializationVersion;
            this.estimatePacketSize = estimatePacketSize;
            this.stringCharEstimatedSizeInBytes = stringCharEstimatedSizeInBytes;
        }

        /// <summary>
        /// Gets or sets the serialization version with which serializer is currently configured.
        /// </summary>
        public ushort SerializationVersion
        {
            get
            {
                return this.version;
            }

            set
            {
                if (value > MaxVersion)
                {
                    throw new ArgumentException("Version number is greated than maximum current version supported: " + MaxVersion, nameof(value));
                }

                this.version = value;
            }
        }

        /// <summary>
        /// Gets the expected size of the package in current serialization state.
        /// </summary>
        public long ExpectedPackageSize
        {
            get
            {
                if (this.estimatePacketSize)
                {
                    return this.currentMetricDataBlockSize + this.currentMetadataDictionaryBlockSize + this.currentStringDictionaryBlockSize +
                           SerializationUtils.EstimateUInt32InBase128Size((uint)this.strings.Count) +
                           SerializationUtils.EstimateUInt32InBase128Size((uint)this.metadatas.Count);
                }

                throw new InvalidOperationException("Packet size estimation is not enabled, use estimatePacketSize:true when creating the serializer");
            }
        }

        /// <summary>
        /// Serializes counter (metric) data to the stream.
        /// </summary>
        /// <param name="stream">Stream to which data should be serialized. Stream should be writable and provide random access.</param>
        /// <param name="metricData">Collection of metric data to be serialized.</param>
        public void Serialize(Stream stream, IEnumerable<IReadOnlyMetric> metricData)
        {
            if (!stream.CanWrite || !stream.CanSeek)
            {
                throw new ArgumentException("Stream should be writable and provide random access.", nameof(stream));
            }

            try
            {
                using (var writer = new NoCloseBinaryWriter(stream, Encoding.UTF8))
                {
                    var startStreamPosition = stream.Position;

                    // Write version and type serializers info
                    writer.Write(this.version);

                    long crcOffSet = 0;
                    long crcBodyOffSet = 0;
                    if (this.version >= 5)
                    {
                        // Add CRC
                        crcOffSet = stream.Position;
                        writer.Write((uint)0);
                        crcBodyOffSet = stream.Position;
                    }

                    writer.Write(TypeSerializerFlags);

                    // Reserve place to write type serializers data sections offsets
                    var offsetsPosition = stream.Position;
                    stream.Position += 2 * sizeof(long);

                    // Write metrics data
                    this.WriteMetricsData(writer, metricData);

                    // Write cached metrics metadata and offset
                    var serializerDataPosition = stream.Position;
                    stream.Position = offsetsPosition;
                    writer.Write(serializerDataPosition - startStreamPosition);
                    offsetsPosition = stream.Position;
                    stream.Position = serializerDataPosition;
                    SerializationUtils.WriteUInt32AsBase128(writer, (uint)this.metadatas.Count);
                    this.metadatas.ForEach(m => this.WriteMetricMetadata(writer, m));

                    // Write cached strings and offset
                    serializerDataPosition = stream.Position;
                    stream.Position = offsetsPosition;
                    writer.Write(serializerDataPosition - startStreamPosition);
                    stream.Position = serializerDataPosition;
                    SerializationUtils.WriteUInt32AsBase128(writer, (uint)this.strings.Count);
                    this.strings.ForEach(writer.Write);
                    var endOfStream = stream.Position;

                    if (this.version >= 5)
                    {
                        stream.Position = crcBodyOffSet;
                        var crc = Crc.ComputeCrc(0, stream, stream.Length - crcBodyOffSet);
                        stream.Position = crcOffSet;
                        writer.Write(crc);
                    }

                    stream.Position = endOfStream;
                }
            }
            catch (IOException ioException)
            {
                throw new MetricSerializationException("Failed to serialize data.", ioException);
            }
            finally
            {
                this.nextStringIndex = 0;
                this.stringIndexes.Clear();
                this.strings.Clear();
                this.nextMetadataIndex = 0;
                this.metadataIndexes.Clear();
                this.metadatas.Clear();
                this.currentMetricDataBlockSize = 0;
                this.currentMetadataDictionaryBlockSize = 0;
                this.currentStringDictionaryBlockSize = 0;
            }
        }

        private long EstimateStringSize(string value)
        {
            // Assuming 50 % characters are non-ascii
            return (int)(value.Length * this.stringCharEstimatedSizeInBytes) + SerializationUtils.EstimateUInt32InBase128Size((uint)value.Length);
        }

        private void WriteMetricsData(BinaryWriter writer, IEnumerable<IReadOnlyMetric> metricData)
        {
            Stream writerStream = writer.BaseStream;

            long currentTimeInMinutes = 0;
            if (this.version >= 5)
            {
                var currentTimeInTicks = DateTime.UtcNow.Ticks;
                currentTimeInMinutes = (currentTimeInTicks - (currentTimeInTicks % SerializationUtils.OneMinuteInterval)) / SerializationUtils.OneMinuteInterval;
                SerializationUtils.WriteUInt64AsBase128(writer, (ulong)currentTimeInMinutes);
            }

            var metricsCountPosition = writer.BaseStream.Position;
            writer.Write((uint)0);
            uint metricsCount = 0;
            foreach (var data in metricData)
            {
                ++metricsCount;
                var metadata = data.MetricMetadata;

                this.WriteMetricMetadataIndex(writer, metadata);

                // In versions 0-2 Monitoring Account and Metric Namespace was part of the Metric data
                // From version 3 Monitoring Account is removed and Metric Namespace became a part of Metric Metadata
                if (this.version < 3)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.MonitoringAccount));
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.MetricNamespace));
                }

                // In version 0 we had EventId, which was always passed as empty string
                if (this.version == 0)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(string.Empty));
                }

                if (this.version >= 5)
                {
                    var timeTicks = data.TimeUtc.Ticks;
                    long timeInMinutes = (timeTicks - (timeTicks % SerializationUtils.OneMinuteInterval)) / SerializationUtils.OneMinuteInterval;
                    SerializationUtils.WriteInt64AsBase128(writer, currentTimeInMinutes - timeInMinutes);
                }
                else
                {
                    SerializationUtils.WriteUInt64AsBase128(writer, (ulong)data.TimeUtc.Ticks);
                }

                for (byte j = 0; j < data.MetricMetadata.DimensionsCount; ++j)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(data.GetDimensionValue(j)));
                }

                var samplingTypes = data.SamplingTypes;
                bool useDouble = false;
                bool storeDoubleAsLong = false;
                if ((samplingTypes & SamplingTypes.DoubleValueType) != 0)
                {
                    useDouble = true;

                    if (data.SumUnion.CanRepresentDoubleAsLong() &&
                        ((samplingTypes & SamplingTypes.Min) == 0 ||
                         (data.MinUnion.CanRepresentDoubleAsLong() && data.MaxUnion.CanRepresentDoubleAsLong())))
                    {
                        samplingTypes = samplingTypes | SamplingTypes.DoubleValueStoredAsLongType;
                        storeDoubleAsLong = true;
                    }
                }

                SerializationUtils.WriteUInt32AsBase128(writer, (uint)samplingTypes);

                if ((data.SamplingTypes & SamplingTypes.Min) != 0)
                {
                    this.WriteMetricValue(data.MinUnion, useDouble, storeDoubleAsLong, writer);
                }

                if ((data.SamplingTypes & SamplingTypes.Max) != 0)
                {
                    this.WriteMetricValue(data.MaxUnion, useDouble, storeDoubleAsLong, writer);
                }

                if ((data.SamplingTypes & SamplingTypes.Sum) != 0)
                {
                    this.WriteMetricValue(data.SumUnion, useDouble, storeDoubleAsLong, writer);
                }

                if ((data.SamplingTypes & SamplingTypes.Count) != 0)
                {
                    SerializationUtils.WriteUInt32AsBase128(writer, data.Count);
                }

                if ((data.SamplingTypes & SamplingTypes.SumOfSquareDiffFromMean) != 0)
                {
                    writer.Write(data.SumOfSquareDiffFromMean);
                }

                if ((data.SamplingTypes & SamplingTypes.Histogram) != 0)
                {
                    if (data.Histogram == null)
                    {
                        var message = string.Format(
                            CultureInfo.InvariantCulture,
                            "Invalid input data. Declared sampling type contains Histogram, but Histogram data is null. Metric:({0},{1},{2}).",
                            data.MonitoringAccount,
                            data.MetricNamespace,
                            data.MetricMetadata.MetricName);

                        throw new MetricSerializationException(message, null);
                    }

                    SerializationUtils.WriteHistogramDataHistogram(writer, data.Histogram.SamplesCount, data.Histogram.Samples, this.version > 3);
                }

                if ((data.SamplingTypes & SamplingTypes.HyperLogLogSketch) != 0)
                {
                    if (data.HyperLogLogSketchesStream == null && data.HyperLogLogSketches == null)
                    {
                        var message = string.Format(
                            CultureInfo.InvariantCulture,
                            "Invalid input data. Declared sampling type contains sketches, but sketches data is null. Metric:({0},{1},{2}).",
                            data.MonitoringAccount,
                            data.MetricNamespace,
                            data.MetricMetadata.MetricName);
                        throw new MetricSerializationException(message, null);
                    }

                    if (data.HyperLogLogSketchesStream != null)
                    {
                        if (this.tempBuffer == null)
                        {
                            this.tempBuffer = new byte[TempBufferSize];
                        }

                        writer.Write((int)data.HyperLogLogSketchesStream.Length);
                        var sketchStreamStartPosition = data.HyperLogLogSketchesStream.Position;
                        SerializationUtils.ReadFromStream(data.HyperLogLogSketchesStream, writer.BaseStream, (int)data.HyperLogLogSketchesStream.Length, this.tempBuffer);
                        data.HyperLogLogSketchesStream.Position = sketchStreamStartPosition;
                    }
                    else
                    {
                        if (this.hllSerializationVersion == 0)
                        {
                            SerializationUtils.WriteHyperLogLogSketches(writer, data.HyperLogLogSketches.HyperLogLogSketchesCount, data.HyperLogLogSketches.HyperLogLogSketches);
                        }
                        else
                        {
                            SerializationUtils.WriteHyperLogLogSketchesV2(writer, data.HyperLogLogSketches.HyperLogLogSketchesCount, data.HyperLogLogSketches.HyperLogLogSketches);
                        }
                    }
                }

                if (this.version >= 6)
                {
                    if ((data.SamplingTypes & samplingTypes & SamplingTypes.TDigest) != 0)
                    {
                        SerializationUtils.WriteUInt32AsBase128(writer, FrontEndMetricDeserializer<IMetricMetadata>.TDigestPrefixValue);

                        long pos = writerStream.Position;

                        // placeholder for length encoded as 4 bytes
                        writer.Write((ushort)0);
                        writer.Write((ushort)0);

                        data.TDigest.Serialize(writer);
                        long tdigestSerializedLength = writerStream.Position - pos - 4;
                        if (tdigestSerializedLength > ushort.MaxValue)
                        {
                            throw new ArgumentException("TDigest too big");
                        }

                        writerStream.Position = pos;
                        SerializationUtils.WriteUInt32InBase128AsFixed4Bytes(writer, (ushort)tdigestSerializedLength);

                        writerStream.Position += tdigestSerializedLength;
                    }

                    SerializationUtils.WriteUInt32AsBase128(writer, 0);
                }

                this.currentMetricDataBlockSize = writer.BaseStream.Position;
            }

            var currentPosition = writer.BaseStream.Position;
            writer.BaseStream.Position = metricsCountPosition;

            // Versions before 2 used variable number of bytes to write number of serialized metrics data.
            // From version 2 passing IEnumerable<IReadOnlyMetric> is supported, thus number of metrics data
            // is unknown beforehand and we cannot use variable number anymore. Thus we use fixed 4 bytes
            // uint. To keep compatibility with previous versions, while still have ability to serialize
            // variable amount of data, we write uint number in variable manner but with fixed number of bytes.
            if (this.version >= 2)
            {
                writer.Write(metricsCount);
            }
            else
            {
                SerializationUtils.WriteUInt32InBase128AsFixed4Bytes(writer, metricsCount);
            }

            writer.BaseStream.Position = currentPosition;
        }

        private void WriteMetricMetadataIndex(BinaryWriter writer, IMetricMetadata value)
        {
            uint index;
            if (!this.metadataIndexes.TryGetValue(value, out index))
            {
                index = this.nextMetadataIndex++;
                this.metadataIndexes.Add(value, index);
                this.metadatas.Add(value);

                if (this.estimatePacketSize)
                {
                    // In versions 0-2 Metric Namespace was part of the Metric data, from version 3 it became a part of Metric Metadata
                    if (this.version >= 3)
                    {
                        this.currentMetadataDictionaryBlockSize += SerializationUtils.EstimateUInt32InBase128Size(this.RegisterString(value.MetricNamespace));
                    }

                    this.currentMetadataDictionaryBlockSize += SerializationUtils.EstimateUInt32InBase128Size(this.RegisterString(value.MetricName));
                    this.currentMetadataDictionaryBlockSize += SerializationUtils.EstimateUInt32InBase128Size((uint)value.DimensionsCount);
                    for (int i = 0; i < value.DimensionsCount; ++i)
                    {
                        this.currentMetadataDictionaryBlockSize += SerializationUtils.EstimateUInt32InBase128Size(this.RegisterString(value.GetDimensionName(i)));
                    }
                }
            }

            SerializationUtils.WriteUInt32AsBase128(writer, index);
        }

        private void WriteMetricMetadata(BinaryWriter writer, IMetricMetadata value)
        {
            // In versions 0-2 Metric Namespace was part of the Metric data, from version 3 it became a part of Metric Metadata
            if (this.version >= 3)
            {
                SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(value.MetricNamespace));
            }

            SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(value.MetricName));
            SerializationUtils.WriteUInt32AsBase128(writer, (uint)value.DimensionsCount);
            for (var i = 0; i < value.DimensionsCount; ++i)
            {
                SerializationUtils.WriteUInt32AsBase128(writer, this.RegisterString(value.GetDimensionName(i)));
            }
        }

        private void WriteMetricValue(MetricValueV2 value, bool useDouble, bool storeDoubleAsLong, BinaryWriter writer)
        {
            if (useDouble)
            {
                if (storeDoubleAsLong)
                {
                    SerializationUtils.WriteInt64AsBase128(writer, (long)value.ValueAsDouble);
                }
                else
                {
                    writer.Write(value.ValueAsDouble);
                }
            }
            else
            {
                SerializationUtils.WriteUInt64AsBase128(writer, value.ValueAsULong);
            }
        }

        private uint RegisterString(string value)
        {
            uint index;
            value = value ?? string.Empty;
            if (!this.stringIndexes.TryGetValue(value, out index))
            {
                index = this.nextStringIndex++;
                this.stringIndexes.Add(value, index);
                this.strings.Add(value);

                if (this.estimatePacketSize)
                {
                    this.currentStringDictionaryBlockSize += this.EstimateStringSize(value);
                }
            }

            return index;
        }

        private sealed class MetricEqualityComparer : IEqualityComparer<IMetricMetadata>
        {
            public static readonly MetricEqualityComparer Instance = new MetricEqualityComparer();

            public bool Equals(IMetricMetadata x, IMetricMetadata y)
            {
                if (ReferenceEquals(x, y))
                {
                    return true;
                }

                if (x.DimensionsCount != y.DimensionsCount ||
                    !StringComparer.OrdinalIgnoreCase.Equals(x.MetricNamespace, y.MetricNamespace) ||
                    !StringComparer.OrdinalIgnoreCase.Equals(x.MetricName, y.MetricName))
                {
                    return false;
                }

                for (int i = 0; i < x.DimensionsCount; ++i)
                {
                    if (!StringComparer.OrdinalIgnoreCase.Equals(x.GetDimensionName(i), y.GetDimensionName(i)))
                    {
                        return false;
                    }
                }

                return true;
            }

            public int GetHashCode(IMetricMetadata obj)
            {
                return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.MetricName) ^
                       StringComparer.OrdinalIgnoreCase.GetHashCode(obj.MetricNamespace);
            }
        }

        private sealed class NoCloseBinaryWriter : BinaryWriter
        {
            public NoCloseBinaryWriter(Stream stream, Encoding encoding)
                : base(stream, encoding)
            {
            }

            protected override void Dispose(bool disposing)
            {
                this.BaseStream.Flush();
                base.Dispose(false);
            }
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System;
    using System.Net.Http;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Utility;
    using Microsoft.Online.Metrics.Serialization.Configuration;

    using Newtonsoft.Json;

    /// <summary>
    /// The class to read and write metric configurations.
    /// </summary>
    internal sealed class MetricConfigurationManager
    {
#pragma warning disable SA1401 // Fields must be private
        /// <summary>
        /// The relative URL for metrics configuration
        /// </summary>
        public readonly string ConfigRelativeUrl;
#pragma warning restore SA1401 // Fields must be private

        /// <summary>
        /// The HTTP client instance.
        /// </summary>
        private readonly HttpClient httpClient;

        /// <summary>
        /// The connection information
        /// </summary>
        private readonly ConnectionInfo connectionInfo;

        /// <summary>
        /// Initializes a new instance of the <see cref="MetricConfigurationManager" /> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        public MetricConfigurationManager(ConnectionInfo connectionInfo)
        {
            this.connectionInfo = connectionInfo;
            this.ConfigRelativeUrl = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.ConfigRelativeUrl);
            this.httpClient = HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo);
        }

        /// <summary>
        /// Gets the metric configuration.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <returns>The metric configuration.</returns>
        public async Task<MetricConfigurationV2> Get(MetricIdentifier metricIdentifier)
        {
            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}",
                this.connectionInfo.GetEndpoint(metricIdentifier.MonitoringAccount),
                this.ConfigRelativeUrl,
                metricIdentifier.MonitoringAccount,
                SpecialCharsHelper.EscapeTwice(metricIdentifier.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricIdentifier.MetricName));

            var response = await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Get,
                this.httpClient,
                metricIdentifier.MonitoringAccount,
                this.ConfigRelativeUrl).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<MetricConfigurationV2>(response.Item1);
        }

        /// <summary>
        /// Deletes the metric configuration.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <returns>A task representing the deletion of the configuration.</returns>
        /// <remarks>
        /// It deletes only the metric configuration, not the metric data.
        /// </remarks>
        public async Task Delete(MetricIdentifier metricIdentifier)
        {
            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}",
                this.connectionInfo.GetEndpoint(metricIdentifier.MonitoringAccount),
                this.ConfigRelativeUrl,
                metricIdentifier.MonitoringAccount,
                SpecialCharsHelper.EscapeTwice(metricIdentifier.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricIdentifier.MetricName));

            await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Delete,
                this.httpClient,
                metricIdentifier.MonitoringAccount,
                this.ConfigRelativeUrl).ConfigureAwait(false);
        }

        /// <summary>
        /// Posts the metric configuration.
        /// </summary>
        /// <param name="configuration">The metric configuration.</param>
        /// <returns>A task representing the update of the configuration.</returns>
        public async Task Post(MetricConfigurationV2 configuration)
        {
            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}",
                this.connectionInfo.GetEndpoint(configuration.MonitoringAccount),
                this.ConfigRelativeUrl,
                configuration.MonitoringAccount,
                SpecialCharsHelper.EscapeTwice(configuration.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(configuration.MetricName));

            await HttpClientHelper.GetResponse(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                configuration.MonitoringAccount,
                this.ConfigRelativeUrl,
                configuration).ConfigureAwait(false);
        }
    }
}<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="ConsoleLogEngine.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Logging
{
    using System;
    using System.Globalization;

    /// <summary>
    /// Log engine that sends its output to console.
    /// </summary>
    internal sealed class ConsoleLogEngine : ILogEngine
    {
        /// <summary>
        /// Logs the given data according to the engine implementation.
        /// </summary>
        /// <param name="level">
        /// Level of the log statement.
        /// </param>
        /// <param name="logId">
        /// Log identification for classifying log statements.
        /// </param>
        /// <param name="tag">
        /// Extra string that allows another level of classification under the log id.
        /// </param>
        /// <param name="format">
        /// Message to be logged, it can be a format message.
        /// </param>
        /// <param name="objectParams">
        /// Optional, any parameter to be used to build the formatted message string.
        /// </param>
        public void Log(LoggerLevel level, object logId, string tag, string format, params object[] objectParams)
        {
            if (level == LoggerLevel.CustomerFacingInfo)
            {
                var customerFacingLog = string.Format(
                    CultureInfo.InvariantCulture,
                    "[{0}] {1}",
                    DateTime.UtcNow.ToString("hh:mm:ss"),
                    format);

                Console.BackgroundColor = ConsoleColor.Black;

                if (objectParams == null || objectParams.Length == 0)
                {
                    Console.WriteLine(customerFacingLog);
                }
                else
                {
                    Console.WriteLine(customerFacingLog, objectParams);
                }
            }
            else
            {
                var s = string.Format(
                    CultureInfo.InvariantCulture,
                    "UTC=[{0}] Level=[{1}] LogId=[{2}] Tag=[{3}] {4}",
                    DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fff"),
                    level,
                    logId,
                    tag,
                    format);

                if (level <= LoggerLevel.Warning)
                {
                    Console.BackgroundColor = ConsoleColor.Blue;
                }
                else
                {
                    Console.BackgroundColor = ConsoleColor.Black;
                }

                if (objectParams == null || objectParams.Length == 0)
                {
                    Console.WriteLine(s);
                }
                else
                {
                    Console.WriteLine(s, objectParams);
                }

                Console.BackgroundColor = ConsoleColor.Black;
            }
        }

        /// <summary>
        /// Checks if a log statement with the given parameters will be actually logged or
        /// not. Useful to avoid expensive operations for log statements that are going to
        /// be dropped by the log engine.
        /// </summary>
        /// <param name="level">
        /// Level of the log statement.
        /// </param>
        /// <param name="logId">
        /// Log identification for classifying log statements.
        /// </param>
        /// <param name="tag">
        /// Extra string that allows another level of classification under the log id.
        /// </param>
        /// <returns>
        /// True if the statement is going to be logged, false otherwise.
        /// </returns>
        public bool IsLogged(LoggerLevel level, object logId, string tag)
        {
            return true;
        }

        /// <summary>
        /// Disposes resouces used by the object.
        /// </summary>
        public void Dispose()
        {
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ApPkiAcl.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using Newtonsoft.Json;

    /// <summary>
    /// AP PKI ACL with access to MDM.
    /// </summary>
    /// <seealso cref="Microsoft.Cloud.Metrics.Client.Configuration.IPermissionV2" />
    public sealed class ApPkiAcl : IPermissionV2
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ApPkiAcl"/> class.
        /// </summary>
        /// <param name="identity">The AP PKI ACL.</param>
        /// <param name="roleConfiguration">The role granted to this ACL.</param>
        /// <param name="description">The description of ACL. Default value is null.</param>
        [JsonConstructor]
        public ApPkiAcl(string identity, RoleConfiguration roleConfiguration, string description = null)
        {
            if (string.IsNullOrWhiteSpace(identity))
            {
                throw new ArgumentNullException(nameof(identity));
            }

            this.Identity = identity;
            this.Description = description;
            this.RoleConfiguration = roleConfiguration;
        }

        /// <summary>
        /// The identity to grant permission.
        /// </summary>
        public string Identity { get; }

        /// <inheritdoc />
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        public string Description { get; }

        /// <summary>
        /// The level of access to be granted to this identity.
        /// </summary>
        public RoleConfiguration RoleConfiguration { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ConfigurationUpdateResult.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// A class to represent the configuration update result.
    /// </summary>
    public sealed class ConfigurationUpdateResult : IConfigurationUpdateResult
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigurationUpdateResult"/> class.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account name.</param>
        /// <param name="success">Success result of operation.</param>
        /// <param name="message">Exception details if any.</param>
        [JsonConstructor]
        public ConfigurationUpdateResult(
            string monitoringAccount,
            bool success,
            string message)
        {
            this.MonitoringAccount = monitoringAccount;
            this.Success = success;
            this.Message = message;
        }

        /// <summary>
        /// Monitoring account on which configuration was updated.
        /// </summary>
        public string MonitoringAccount { get; }

        /// <summary>
        /// True if configuration is updated successfully. False, otherwise.
        /// </summary>
        public bool Success { get; set; }

        /// <summary>
        /// Exception details in case of failures.
        /// </summary>
        public string Message { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MonitoringAccount.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Newtonsoft.Json;

    /// <summary>
    /// The monitoring account configuration object used for JSON serialization and deserialization.
    /// </summary>
    public sealed class MonitoringAccount : IMonitoringAccount
    {
        /// <summary>
        /// List of permissions granted access to this account.
        /// </summary>
        private readonly IList<IPermissionV2> permissions;

        /// <summary>
        /// List of mirror monitoring accounts.
        /// </summary>
        private readonly IList<string> mirrorMonitoringAccountList;

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitoringAccount"/> class.
        /// </summary>
        /// <param name="name">The name of the account.</param>
        /// <param name="description">The description of the account.</param>
        /// <param name="permissionsV2">The permissions associated with the account.</param>
        public MonitoringAccount(
            string name,
            string description,
            IEnumerable<IPermissionV2> permissionsV2)
            : this(name, null, description, default(DateTime), permissionsV2, null, 1, null, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitoringAccount" /> class.
        /// </summary>
        /// <param name="name">The name of the account.</param>
        /// <param name="displayName">The display name of the account.</param>
        /// <param name="description">The description of the account.</param>
        /// <param name="lastUpdatedTimeUtc">The last updated time.</param>
        /// <param name="permissionsV2">The permissions associated with the account.</param>
        /// <param name="lastUpdatedBy">The identity that last updated the account.</param>
        /// <param name="version">The version of the account.</param>
        /// <param name="mirrorMonitoringAccountList">The list of mirror monitoring accounts.</param>
        /// <param name="homeStampHostName">Name of the home stamp host for this account.</param>
        [JsonConstructor]
        internal MonitoringAccount(
            string name,
            string displayName,
            string description,
            DateTime lastUpdatedTimeUtc,
            IEnumerable<IPermissionV2> permissionsV2,
            string lastUpdatedBy,
            uint version,
            IEnumerable<string> mirrorMonitoringAccountList,
            string homeStampHostName)
        {
            if (permissionsV2 == null)
            {
                throw new ArgumentNullException(nameof(permissionsV2));
            }

            this.Name = name;
            this.DisplayName = displayName;
            this.Description = description;
            this.LastUpdatedTimeUtc = lastUpdatedTimeUtc;
            this.LastUpdatedBy = lastUpdatedBy;
            this.Version = version;
            this.permissions = permissionsV2.ToList();
            this.HomeStampHostName = homeStampHostName;

            this.mirrorMonitoringAccountList = mirrorMonitoringAccountList?.ToList() ?? new List<string>();
        }

        /// <summary>
        /// The name of the monitoring account.
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// The display name of the monitoring account.
        /// </summary>
        public string DisplayName { get; set; }

        /// <summary>
        /// The description of the monitoring account.
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// The host name of the MDM stamp that currently owns this account.
        /// </summary>
        public string HomeStampHostName { get; }

        /// <summary>
        /// The list of entities that have access to this MDM account and their roles.
        /// </summary>
        [JsonProperty(PropertyName = "PermissionsV2")]
        public IEnumerable<IPermissionV2> Permissions
        {
            get { return this.permissions; }
        }

        /// <summary>
        /// The time the account was last updated.
        /// </summary>
        public DateTime LastUpdatedTimeUtc { get; }

        /// <summary>
        /// The identity that updated the account most recently.
        /// </summary>
        public string LastUpdatedBy { get; }

        /// <summary>
        /// The version of the monitoring account configuration.
        /// </summary>
        public uint Version { get; }

        /// <inheritdoc />
        public IEnumerable<string> MirrorMonitoringAccountList
        {
            get
            {
                return this.mirrorMonitoringAccountList;
            }
        }

        /// <summary>
        /// Gets or sets the value for the maximum age, as a <see cref="TimeSpan"/>, of metrics to be accepted.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        internal TimeSpan MaxMetricAge { get; set; }

        /// <summary>
        /// Gets or sets a flag indicating whether to prefer to create new pre-aggregates on metrics store.
        /// </summary>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        internal bool? PreferNewPreaggregateOnMetricsStore { get; set; }

        /// <summary>
        /// Adds a permission to the account.
        /// </summary>
        /// <param name="permission">Permission to add.</param>
        public void AddPermission(IPermissionV2 permission)
        {
           this.permissions.Add(permission);
        }

        /// <summary>
        /// Remove permission from the account.
        /// </summary>
        /// <param name="permission">Permission to remove.</param>
        public void RemovePermission(IPermissionV2 permission)
        {
            this.permissions.Remove(permission);
        }

        /// <inheritdoc />
        public void AddMirrorMonitoringAccount(string monitoringAccountName)
        {
            this.mirrorMonitoringAccountList.Add(monitoringAccountName);
        }

        /// <inheritdoc />
        public void RemoveMirrorMonitoringAccount(string monitoringAccountName)
        {
            this.mirrorMonitoringAccountList.Remove(monitoringAccountName);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RoleConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// The configuration for the MDM role which grants certain premissions to entities using the system.
    /// </summary>
    public sealed class RoleConfiguration
    {
        /// <summary>
        /// This role is meant for a certificate only since users all have read access.
        /// </summary>
        public static readonly RoleConfiguration ReadOnly = new RoleConfiguration("ReadOnly");

        /// <summary>
        /// This role is meant for a certificate primarily but does not need to be restricted to such.
        /// Allows a certificate to read or publish data only but not be used for account settings modification.
        /// </summary>
        public static readonly RoleConfiguration MetricPublisher = new RoleConfiguration("MetricPublisher");

        /// <summary>
        /// This role has the ability to modify and create dashboards within an account.
        /// </summary>
        public static readonly RoleConfiguration DashboardEditor = new RoleConfiguration("DashboardEditor");

        /// <summary>
        /// This role has the ability to modify and create monitors within an account.
        /// </summary>
        public static readonly RoleConfiguration MonitorEditor = new RoleConfiguration("MonitorEditor");

        /// <summary>
        /// This role has the ability to modify and create monitors/metrics within an account.
        /// </summary>
        public static readonly RoleConfiguration MetricAndMonitorEditor = new RoleConfiguration("MetricAndMonitorEditor");

        /// <summary>
        /// This role has the ability to modify metric, monitor or health configuration within an account.
        /// </summary>
        public static readonly RoleConfiguration ConfigurationEditor = new RoleConfiguration("ConfigurationEditor");

        /// <summary>
        /// Full access to modify configuration, account settings and dashboards.
        /// </summary>
        public static readonly RoleConfiguration Administrator = new RoleConfiguration("Administrator");

        /// <summary>
        /// Initializes a new instance of the <see cref="RoleConfiguration"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        public RoleConfiguration(string name)
        {
            this.Name = name;
        }

        /// <summary>
        /// Gets the name of the role.
        /// </summary>
        public string Name { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MetricEnrichmentTransformationType.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricEnrichmentRuleManagement
{
    /// <summary>
    /// Represents the metric enrichment transformation type.
    /// </summary>
    public enum MetricEnrichmentTransformationType
    {
        /// <summary>
        /// Adds new dimensions to the event.
        /// </summary>
        Add,

        /// <summary>
        /// Replaces existing dimension in the event.
        /// </summary>
        Replace,

        /// <summary>
        /// Drops the event.
        /// </summary>
        Drop
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="QueryResultsList.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    using Utility;

    /// <summary>
    /// Represents a list of IQueryResult
    /// </summary>
    public sealed class QueryResultsList
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="QueryResultsList"/> class.
        /// </summary>
        /// <param name="startTimeUtc">The start time.</param>
        /// <param name="endTimeUtc">The end time.</param>
        /// <param name="timeResolutionInMilliseconds">The time resolution in milliseconds.</param>
        /// <param name="results">The results.</param>
        [JsonConstructor]
        internal QueryResultsList(long startTimeUtc, long endTimeUtc, long timeResolutionInMilliseconds, IReadOnlyList<QueryResult> results)
        {
            this.StartTimeUtc = UnixEpochHelper.FromMillis(startTimeUtc);
            this.EndTimeUtc = UnixEpochHelper.FromMillis(endTimeUtc);
            this.TimeResolutionInMilliseconds = timeResolutionInMilliseconds;
            this.Results = results;
        }

        /// <summary>
        /// Gets the end time in UTC for the query results.
        /// </summary>
        public DateTime EndTimeUtc { get; private set; }

        /// <summary>
        /// Gets the start time in UTC for the query results.
        /// </summary>
        public DateTime StartTimeUtc { get; private set; }

        /// <summary>
        /// Gets the time resolution in milliseconds for the query results.
        /// </summary>
        public long TimeResolutionInMilliseconds { get; private set; }

        /// <summary>
        /// Gets the query results. Each result represent a single time series where start time, end time and time resolution
        /// is represented by this object members.
        /// </summary>
        public IReadOnlyList<IQueryResult> Results { get; private set; }
    }
}
<file_sep>﻿// <copyright file="MetricSerializationException.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// <author email="selavrin">
//     <NAME>
// </author>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;

    /// <summary>
    /// A general exception used to report about metric serialization or deserialization failures.
    /// </summary>
    [Serializable]
    public class MetricSerializationException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MetricSerializationException"/> class.
        /// </summary>
        /// <param name="message">Message describing exception situation.</param>
        /// <param name="innerException">Inner exception which caused exception situation.</param>
        /// <param name="isInvalidData">True if the exception is because of invalid data in the packet.</param>
        public MetricSerializationException(string message, Exception innerException, bool isInvalidData = false)
            : base(message, innerException)
        {
            this.IsInvalidData = true;
        }

        /// <summary>
        /// Gets a value indicating whether the failure is because of invalid data in the packet.
        /// </summary>
        public bool IsInvalidData { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IMetricReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Query;
    using Microsoft.Online.Metrics.Serialization.Configuration;

    /// <summary>
    /// The interface for reading metric data.
    /// </summary>
    public interface IMetricReader
    {
        /// <summary>
        /// Gets the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="definition">The time series definition.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definition"/>.
        /// </returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<TimeSeries<MetricIdentifier, double?>> GetTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            TimeSeriesDefinition<MetricIdentifier> definition);

        /// <summary>
        /// Gets the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="definition">The time series definition.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definition"/>.
        /// </returns>
        /// <remarks>This API uses <see cref="AggregationType.Automatic"/> by default and other overloads are available for specific <see cref="AggregationType"/>.</remarks>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<TimeSeries<MetricIdentifier, double?>> GetTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            int seriesResolutionInMinutes,
            TimeSeriesDefinition<MetricIdentifier> definition);

        /// <summary>
        /// Gets time series with multiple sampling types.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="definition">The time series definition.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="aggregationType">The aggregation function used to reduce the resolution of the returned series.</param>
        /// <returns>
        /// The time series for the given <paramref name="definition" />.
        /// </returns>
        Task<TimeSeries<MetricIdentifier, double?>> GetTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType[] samplingTypes,
            TimeSeriesDefinition<MetricIdentifier> definition,
            int seriesResolutionInMinutes = 1,
            AggregationType aggregationType = AggregationType.Automatic);

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions"/>.
        /// </returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            params TimeSeriesDefinition<MetricIdentifier>[] definitions);

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions"/>.
        /// </returns>
        /// <remarks>This API uses <see cref="AggregationType.Automatic"/> by default and other overloads are available for specific <see cref="AggregationType"/>.</remarks>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            int seriesResolutionInMinutes,
            params TimeSeriesDefinition<MetricIdentifier>[] definitions);

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions"/>.
        /// </returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions);

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <remarks>This API uses <see cref="AggregationType.Automatic"/> by default and other overloads are available for specific <see cref="AggregationType"/>.</remarks>
        /// <returns>
        /// The time series of for the given <paramref name="definitions"/>.
        /// </returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            int seriesResolutionInMinutes,
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions);

        /// <summary>
        /// Gets a list of the time series, each with multiple sampling types.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="definitions">The time series definitions.</param>
        /// <param name="seriesResolutionInMinutes">The resolution window used to reduce the resolution of the returned series.</param>
        /// <param name="aggregationType">The aggregation function used to reduce the resolution of the returned series.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions" />.
        /// </returns>
        Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType[] samplingTypes,
            IEnumerable<TimeSeriesDefinition<MetricIdentifier>> definitions,
            int seriesResolutionInMinutes = 1,
            AggregationType aggregationType = AggregationType.Automatic);

        /// <summary>
        /// Gets a list of the time series.
        /// </summary>
        /// <param name="definitions">The time series definitions.</param>
        /// <returns>
        /// The time series of for the given <paramref name="definitions"/>.
        /// </returns>
        Task<IReadOnlyList<TimeSeries<MetricIdentifier, double?>>> GetMultipleTimeSeriesAsync(IList<TimeSeriesDefinition<MetricIdentifier>> definitions);

        /// <summary>
        /// Gets the list of namespaces for the <paramref name="monitoringAccount"/>.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <returns>The list of namespaces for the <paramref name="monitoringAccount"/>.</returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<string>> GetNamespacesAsync(string monitoringAccount);

        /// <summary>
        /// Gets the list of metric names for the <paramref name="monitoringAccount" /> and <paramref name="metricNamespace" />.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <returns>
        /// The list of metric names for the <paramref name="monitoringAccount" /> and <paramref name="metricNamespace" />.
        /// </returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<string>> GetMetricNamesAsync(string monitoringAccount, string metricNamespace);

        /// <summary>
        /// Gets the list of dimension names for the <paramref name="metricId" />.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <returns>
        /// The list of dimension names for the <paramref name="metricId" />.
        /// </returns>
        Task<IReadOnlyList<string>> GetDimensionNamesAsync(MetricIdentifier metricId);

        /// <summary>
        /// Gets the list of pre-aggregate configurations for the <paramref name="metricId" />.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <returns>
        /// The list of pre-aggregate configurations for the <paramref name="metricId" />.
        /// </returns>
        Task<IReadOnlyList<PreAggregateConfiguration>> GetPreAggregateConfigurationsAsync(MetricIdentifier metricId);

        /// <summary>
        /// Gets the known dimension combinations.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <returns>Time series definitions with  known dimension combinations.</returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>>> GetKnownTimeSeriesDefinitionsAsync(
            MetricIdentifier metricId,
            params DimensionFilter[] dimensionFilters);

        /// <summary>
        /// Gets the known dimension combinations.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <returns>Time series definitions with  known dimension combinations.</returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>>> GetKnownTimeSeriesDefinitionsAsync(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters);

        /// <summary>
        /// Gets the dimension values for <paramref name="dimensionName"/> satifying the <paramref name="dimensionFilters"/>
        /// and time range (<paramref name="startTimeUtc"/>, <paramref name="endTimeUtc"/>)
        /// </summary>
        /// <remarks>
        /// Time range resolution is day.
        /// </remarks>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">
        /// The dimension filters representing the pre-aggregate dimensions.
        /// Create an emtpy include filter for dimension with no filter values.
        /// Requested dimension should also be part of this and should be empty.
        /// </param>
        /// <param name="dimensionName">Name of the dimension for which values are requested.</param>
        /// <param name="startTimeUtc">Start time for evaluating dimension values.</param>
        /// <param name="endTimeUtc">End time for evaluating dimension values.</param>
        /// <returns>Dimension values for <paramref name="dimensionName"/>.</returns>
        Task<IReadOnlyList<string>> GetDimensionValuesAsync(
            MetricIdentifier metricId,
            List<DimensionFilter> dimensionFilters,
            string dimensionName,
            DateTime startTimeUtc,
            DateTime endTimeUtc);

        /// <summary>
        /// Gets the known dimension combinations.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">Start time for dimension values.</param>
        /// <param name="endTimeUtc">End time for dimension values.</param>
        /// <param name="newCombinationsOnly">
        /// If true only combinations which were added into memory in this session of hinting system after fromTimeUtc.
        /// This flag does *not* guarantee that only new combinations will be returned
        /// It is more of a hint to the hinting system to try to give only new combinations in given time range.
        /// </param>
        /// <returns>Time series definitions with  known dimension combinations.</returns>
        /// <exception cref="MetricsClientException">
        /// This exception is thrown on all failures in communication.
        /// One should look at inner exception for more details.
        /// </exception>
        Task<IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>>> GetKnownTimeSeriesDefinitionsAsync(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            bool newCombinationsOnly = false);

        /// <summary>
        /// Gets the filtered dimension values asynchronously.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="reducer">The reducing function to apply to the time series.</param>
        /// <param name="queryFilter">The query filter.</param>
        /// <param name="includeSeries">if set to <c>true</c> include series values.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.</param>
        /// <returns>A list of filtered dimension values.</returns>
        [Obsolete]
        Task<IReadOnlyList<IQueryResult>> GetFilteredDimensionValuesAsync(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            Reducer reducer,
            QueryFilter queryFilter,
            bool includeSeries,
            SelectionClause selectionClause = null,
            AggregationType aggregationType = AggregationType.Sum,
            long seriesResolutionInMinutes = 1);

        /// <summary>
        /// Gets the filtered dimension values asynchronously.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters.</param>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="samplingType">The sampling type.</param>
        /// <param name="reducer">The reducing function to apply to the time series.</param>
        /// <param name="queryFilter">The query filter.</param>
        /// <param name="includeSeries">if set to <c>true</c> include series values.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.</param>
        /// <returns>A list of filtered dimension values.</returns>
        [Obsolete]
        Task<QueryResultsList> GetFilteredDimensionValuesAsyncV2(
            MetricIdentifier metricId,
            IEnumerable<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            SamplingType samplingType,
            Reducer reducer,
            QueryFilter queryFilter,
            bool includeSeries,
            SelectionClause selectionClause = null,
            AggregationType aggregationType = AggregationType.Sum,
            long seriesResolutionInMinutes = 1);

        /// <summary>
        /// Gets the time series values that match the filtering criteria.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.  By default, all results are returned.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.  By default, automatic resolution is used (same as Jarvis UI).</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.  1 minute resolution (full resolution in MDM today) by default.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <param name="outputDimensionNames">The output dimension names.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.</param>
        /// <returns>
        /// Time series definitions matching the query criteria.
        /// </returns>
        [Obsolete]
        Task<IQueryResultListV3> GetFilteredDimensionValuesAsyncV3(
            MetricIdentifier metricId,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            IReadOnlyList<SamplingType> samplingTypes,
            SelectionClauseV3 selectionClause = null,
            AggregationType aggregationType = AggregationType.Automatic,
            long seriesResolutionInMinutes = 1,
            Guid? traceId = null,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false);

        /// <summary>
        /// Gets the time series values that match the filtering criteria.
        /// Note: This API is for advanced scenarios only. Use it only when you fetch huge amounts of metrics from
        /// multiple stamps in parallel and face performance problems related to memory usage
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.  By default, all results are returned.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.  By default, automatic resolution is used (same as Jarvis UI).</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.  1 minute resolution (full resolution in MDM today) by default.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <param name="outputDimensionNames">The output dimension names.</param>
        /// <returns>
        /// Time HTTP response
        /// </returns>
        Task<HttpResponseMessage> GetTimeSeriesStreamedAsync(
            MetricIdentifier metricId,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            IReadOnlyList<SamplingType> samplingTypes,
            SelectionClauseV3 selectionClause = null,
            AggregationType aggregationType = AggregationType.Automatic,
            long seriesResolutionInMinutes = 1,
            Guid? traceId = null,
            IReadOnlyList<string> outputDimensionNames = null);

        /// <summary>
        /// Gets the time series values that match the filtering criteria.
        /// </summary>
        /// <param name="metricId">The metric identifier.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="samplingTypes">The sampling types.</param>
        /// <param name="selectionClause">Reduce result to top N results of the query.  By default, all results are returned.</param>
        /// <param name="aggregationType">Aggregation function to use when reducing the resolution of the returned series.  By default, automatic resolution is used (same as Jarvis UI).</param>
        /// <param name="seriesResolutionInMinutes">Reduce size of included series array by adjusting the resolution.  1 minute resolution (full resolution in MDM today) by default.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <param name="outputDimensionNames">The output dimension names.</param>
        /// <param name="lastValueMode">Indicating if the query should be fulfilled with last value mode. If true, null values in the query range requested will be filled with the last known value.</param>
        /// <returns>
        /// Time series definitions matching the query criteria.
        /// </returns>
        Task<IQueryResultListV3> GetTimeSeriesAsync(
            MetricIdentifier metricId,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            IReadOnlyList<SamplingType> samplingTypes,
            SelectionClauseV3 selectionClause = null,
            AggregationType aggregationType = AggregationType.Automatic,
            long seriesResolutionInMinutes = 1,
            Guid? traceId = null,
            IReadOnlyList<string> outputDimensionNames = null,
            bool lastValueMode = false);

        /// <summary>
        /// Returns all metric definitions for time series satisfying given set of filters in the given monitoring account.
        /// </summary>
        /// <remarks>
        /// QOS metrics, composite metrics and wild card metrics are not included in the result set.
        /// </remarks>
        /// <param name="monitoringAccount">Monitoring account name.</param>
        /// <param name="dimensionFilters">The dimension filters, used to indicate one or more possible dimension values.</param>
        /// <param name="startTimeUtc">Start time for time series.</param>
        /// <param name="endTimeUtc">End time for time series.</param>
        /// <param name="traceId">The trace identifier for the query, used for diagnostic purposes only.  If a trace id is not provided, one will be generated.</param>
        /// <returns>
        /// All metric definitions which has data for time series keys satisfy the given set of filters.
        /// </returns>
        Task<IReadOnlyList<MetricDefinitionV2>> GetMetricDefinitionsAsync(
            string monitoringAccount,
            IReadOnlyList<DimensionFilter> dimensionFilters,
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            Guid? traceId = null);
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="PublicationConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// Determine which metric storage should be used for the data from this preaggregate.
    /// </summary>
    public sealed class PublicationConfiguration : IPublicationConfiguration
    {
        /// <summary>
        /// Data store configuration where data will publish to metric store only.
        /// </summary>
        public static readonly PublicationConfiguration MetricStore = new PublicationConfiguration(true, true, false);

        /// <summary>
        /// Data store configuration where data will publish to cache server only.
        /// </summary>
        public static readonly PublicationConfiguration CacheServer = new PublicationConfiguration(false, false, false);

        /// <summary>
        /// Data store configuration where data will publish to cache server and metric store.
        /// </summary>
        public static readonly PublicationConfiguration CacheServerAndRawMetricsStore = new PublicationConfiguration(true, false, false);

        /// <summary>
        /// Data store configuration where data will publish to cache server and metric store as an aggregated metric.
        /// </summary>
        public static readonly PublicationConfiguration CacheServerAndAggregatedMetricsStore = new PublicationConfiguration(true, false, true);

        /// <summary>
        /// Data store configuration where data will publish to metrics store as an aggregated metric only.
        /// </summary>
        public static readonly PublicationConfiguration AggregatedMetricsStore = new PublicationConfiguration(true, true, true);

        /// <summary>
        /// Initializes a new instance of the <see cref="PublicationConfiguration" /> class.
        /// </summary>
        /// <param name="metricStorePublicationEnabled">If metric store publication is enabled.</param>
        /// <param name="cacheServerPublicationDisabled">IF cache server publication is disabled.</param>
        /// <param name="aggregatedMetricsStorePublication">If aggregated metrics store publication is enabled.</param>
        [JsonConstructor]
        internal PublicationConfiguration(bool metricStorePublicationEnabled, bool cacheServerPublicationDisabled, bool aggregatedMetricsStorePublication)
        {
            this.MetricStorePublicationEnabled = metricStorePublicationEnabled;
            this.CacheServerPublicationDisabled = cacheServerPublicationDisabled;
            this.AggregatedMetricsStorePublication = aggregatedMetricsStorePublication;
        }

        /// <summary>
        /// Determines if the metrics store is enabled or disabled.
        /// </summary>
        public bool MetricStorePublicationEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether cache server publication is disabled.
        /// </summary>
        public bool CacheServerPublicationDisabled { get; }

        /// <summary>
        /// Gets a value indicating whether the preaggregate should be published as an aggregated metrics store metric.
        /// </summary>
        public bool AggregatedMetricsStorePublication { get; }
    }
}<file_sep>﻿// <copyright file="StampLocatorActivity.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>

namespace Microsoft.Cloud.Metrics.Client.ThirdParty
{
    /// <summary>
    /// The stamp locator activities.
    /// </summary>
    public enum StampLocatorActivity
    {
        /// <summary>
        /// Refreshing the region stamp map from the MDM backend API.
        /// </summary>
        StartToRefrehRegionStampMap,
        FinishedRefreshingRegionStampMap,
        FailedToRefrehRegionStampMap,

        /// <summary>
        /// Loading the region stamp map from the local file regionStampMap.json.
        /// </summary>
        StartToLoadRegionStampMapFromLocalFile,
        FinishedLoadingRegionStampMapFromLocalFile,
        FailedToLoadRegionStampMapFromLocalFile,

        /// <summary>
        /// Writing the region stamp map to the local file regionStampMap.json.
        /// </summary>
        StartToWriteRegionStampMapToLocalFile,
        FinishedWritingRegionStampMapToLocalFile,
        FailedToWriteRegionStampMapToLocalFile,
    }
}
<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="FileNamePathHelper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Utility
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;

    /// <summary>
    /// The helper class for file names and paths.
    /// </summary>
    internal static class FileNamePathHelper
    {
        /// <summary>
        /// The default maximum file name allowed.
        /// </summary>
        internal const int MaximumFileNameAllowed = 256;

        /// <summary>
        /// The json file extension.
        /// </summary>
        internal const string JsonFileExtension = ".json";

        /// <summary>
        /// The javascript file extension.
        /// </summary>
        private const string JsFileExtension = ".js";

        /// <summary>
        /// The sorted invalid file chars.
        /// </summary>
        private static readonly char[] SortedInvalidFileChars;

        /// <summary>
        /// Initializes static members of the <see cref="FileNamePathHelper"/> class.
        /// </summary>
        static FileNamePathHelper()
        {
            SortedInvalidFileChars = Path.GetInvalidFileNameChars();
            Array.Sort(SortedInvalidFileChars);
        }

        /// <summary>
        /// Constructs the name of the valid file by joining the provided parameters with "_" while replacing invalid chars with "^".
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metric">The metric.</param>
        /// <param name="monitorId">The monitor identifier.</param>
        /// <param name="fileExtension">The file extension.</param>
        /// <param name="maximumFileNameAllowed">The maximum length of the file name that will be returned.</param>
        /// <returns>
        /// A valid file name.
        /// </returns>
        [SuppressMessage("Microsoft.Security.Cryptography", "CA5354:SHA1CannotBeUsed", Justification = "Used for converting long string to short string, not for security or comparison")]
        internal static string ConstructValidFileName(string monitoringAccount, string metricNamespace, string metric, string monitorId, string fileExtension, int maximumFileNameAllowed)
        {
            // To provide better back compat, only modify if it exceeds the max allowed in the first place.
            var builder = new StringBuilder(maximumFileNameAllowed);
            builder.Append(monitoringAccount);

            if (!string.IsNullOrWhiteSpace(metricNamespace))
            {
                builder.Append('_').Append(metricNamespace);
            }

            if (!string.IsNullOrWhiteSpace(metric))
            {
                builder.Append('_').Append(metric);
            }

            if (!string.IsNullOrWhiteSpace(monitorId))
            {
                builder.Append('_').Append(monitorId);
            }

            var longFileNameString = builder.ToString();

            var requiresHashing = builder.Length + fileExtension.Length > maximumFileNameAllowed;
            if (requiresHashing)
            {
                builder.Clear();
                const int hashValueLength = 16;
                var totalNameLength = maximumFileNameAllowed - fileExtension.Length - hashValueLength - 1;

                // Construct the allowed space for each part remaining (monitoring account always is included)
                var providedParts = 1;
                if (!string.IsNullOrWhiteSpace(metricNamespace))
                {
                    ++providedParts;
                }

                if (!string.IsNullOrWhiteSpace(metric))
                {
                    ++providedParts;
                }

                if (!string.IsNullOrWhiteSpace(monitorId))
                {
                    ++providedParts;
                }

                AppendShortedFilePart(builder, monitoringAccount, totalNameLength, ref providedParts);
                AppendShortedFilePart(builder, metricNamespace, totalNameLength, ref providedParts);
                AppendShortedFilePart(builder, metric, totalNameLength, ref providedParts);
                AppendShortedFilePart(builder, monitorId, totalNameLength, ref providedParts);
            }

            RepalceInvalidFileChars(builder);

            if (!requiresHashing)
            {
                return builder.Append(fileExtension).ToString();
            }

            var shortFileNameString = builder.ToString();
            builder.Clear();
            using (var hashGenerator = SHA1.Create())
            {
                // Using only first 8 bytes of hash as 'good enough' in this case
                var data = hashGenerator.ComputeHash(Encoding.UTF8.GetBytes(longFileNameString.ToLowerInvariant()));
                for (var i = 0; i < data.Length && i < 8; ++i)
                {
                    builder.Append(data[i].ToString("x2"));
                }
            }

            return shortFileNameString + "_" + builder + fileExtension;
        }

        /// <summary>
        /// Constructs the name of the valid file by joining the provided parameters with "_" while replacing invalid chars with "^".
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="maximumFileNameAllowed">The maximum length of the file name that will be returned.</param>
        /// <returns>
        /// A valid file name.
        /// </returns>
        internal static string ConstructValidFileName(string monitoringAccount, int maximumFileNameAllowed)
        {
            return ConstructValidFileName(monitoringAccount, string.Empty, string.Empty, string.Empty, JsonFileExtension, maximumFileNameAllowed);
        }

        /// <summary>
        /// Convert path to valid folder name
        /// </summary>
        /// <param name="path">The path needs to be converted</param>
        /// <returns>Valid folder name</returns>
        internal static string ConvertPathToValidFolderName(string path)
        {
            var builder = new StringBuilder(path);
            RepalceInvalidFileChars(builder);
            return builder.ToString();
        }

        /// <summary>
        /// Appends the shorted file part to the final string.
        /// </summary>
        /// <param name="builder">The builder containing the final string.</param>
        /// <param name="value">The value to add to the string.</param>
        /// <param name="totalAllowedLength">Total length of the final string allowed.</param>
        /// <param name="partsRemaining">The parts remaining that will be added to the final string.</param>
        private static void AppendShortedFilePart(StringBuilder builder, string value, int totalAllowedLength, ref int partsRemaining)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                // Do not count this as a part since it was empty to begin with.
                return;
            }

            var currentMaxAllowed = (totalAllowedLength - builder.Length) / partsRemaining;
            --partsRemaining;
            if (value.Length > currentMaxAllowed)
            {
                var partLengths = (currentMaxAllowed - 1) / 2;
                builder.AppendFormat(
                    "{0}{1}~{2}",
                    builder.Length == 0 ? string.Empty : "_",
                    value.Substring(0, partLengths),
                    value.Substring(value.Length - partLengths));
            }
            else
            {
                builder.AppendFormat(
                    "{0}{1}",
                    builder.Length == 0 ? string.Empty : "_",
                    value);
            }
        }

        /// <summary>
        /// Replace invalid file characters
        /// </summary>
        /// <param name="builder">string builder</param>
        private static void RepalceInvalidFileChars(StringBuilder builder)
        {
            for (int i = 0; i < builder.Length; ++i)
            {
                if (!char.IsLetter(builder[i]) && !char.IsDigit(builder[i]))
                {
                    if (Array.BinarySearch(SortedInvalidFileChars, builder[i]) >= 0)
                    {
                        builder[i] = '^';
                    }
                }
            }
        }
    }
}<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="IMetricConfigurationManager.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;

    /// <summary>
    /// This interface that manages metric configurations.
    /// </summary>
    public interface IMetricConfigurationManager
    {
        /// <summary>
        /// Get the metric specified by the account, namespace and metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">The metric name.</param>
        /// <returns>The metric.</returns>
        Task<IMetricConfiguration> GetAsync(IMonitoringAccount monitoringAccount, string metricNamespace, string metricName);

        /// <summary>
        /// Get the metric specified by the account, namespace and metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="returnEmptyConfig">Determine if empty, unmodified configurations should be returned.</param>
        /// <returns>The metrics that match the criteria.</returns>
        Task<IReadOnlyList<IMetricConfiguration>> GetMultipleAsync(IMonitoringAccount monitoringAccount, string metricNamespace, bool returnEmptyConfig = false);

        /// <summary>
        /// Get the metric specified by the account, namespace and metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="returnEmptyConfig">Determine if empty, unmodified configurations should be returned.</param>
        /// <returns>The metrics that match the criteria.</returns>
        Task<IReadOnlyList<IMetricConfiguration>> GetMultipleAsync(IMonitoringAccount monitoringAccount, bool returnEmptyConfig = false);

        /// <summary>
        /// Save the metric configuration provided.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account configuration.</param>
        /// <param name="metricConfiguration">The metric to be saved.</param>
        /// <param name="skipVersionCheck">Flag indicating whether or not the version flag should be honored.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task SaveAsync(IMonitoringAccount monitoringAccount, IMetricConfiguration metricConfiguration, bool skipVersionCheck = false);

        /// <summary>
        /// Deletes the metric configuration by metric name.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <returns>A task the caller can wait on.</returns>
        Task DeleteAsync(IMonitoringAccount monitoringAccount, string metricNamespace, string metricName);

        /// <summary>
        /// Sync all metric configuration across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <returns>A list of configuration update result.</returns>
        Task<IReadOnlyList<IConfigurationUpdateResult>> SyncAllAsync(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false);

        /// <summary>
        /// Sync all metric configuration across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="metricNamespace">Metric namespace.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <returns>A list of configuration update result.</returns>
        Task<IReadOnlyList<IConfigurationUpdateResult>> SyncAllAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            bool skipVersionCheck = false);

        /// <summary>
        /// Sync all metric and monitor configuration across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <returns>A list of <see cref="ConfigurationUpdateResultList"/>.</returns>
        Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncAllAsyncV2(
            IMonitoringAccount monitoringAccount,
            bool skipVersionCheck = false);

        /// <summary>
        /// Sync all metric and monitor configuration across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="metricNamespace">Metric namespace.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <returns>A list of <see cref="ConfigurationUpdateResultList"/>.</returns>
        Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncAllAsyncV2(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            bool skipVersionCheck = false);

        /// <summary>
        /// Sync metric and monitor configuration for given metric name across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">Monitoring account.</param>
        /// <param name="metricNamespace">Metric namespace.</param>
        /// <param name="metricName">Metric name.</param>
        /// <param name="skipVersionCheck">True if skip version check.</param>
        /// <returns>An instance of <see cref="ConfigurationUpdateResultList"/>.</returns>
        Task<ConfigurationUpdateResultList> SyncConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            string metricName,
            bool skipVersionCheck = false);

        /// <summary>
        /// Download metric configurations as json files.
        /// </summary>
        /// <param name="destinationFolder">folder for storing downloaded config json files.</param>
        /// <param name="monitoringAccount">Monitoring account</param>
        /// <param name="metricNamespace">metric namespace</param>
        /// <param name="metricName">metric name</param>
        /// <param name="metricNameRegex">metric name regex.</param>
        /// <param name="foldersOnNamespacesLevel">indicate if true, config files will be stored under corresponding namespaces folders.</param>
        /// <param name="downloadDefaultMetricConfig">indicate if true, default comfigs will also be downloaded.</param>
        /// <param name="maxFileNameProducedLength">max size of file name that will be created locally.</param>
        /// <returns> OperationStatus.</returns>
        Task<OperationStatus> DownloadMetricConfigurationAsync(
            string destinationFolder,
            IMonitoringAccount monitoringAccount,
            string metricNamespace = null,
            string metricName = null,
            Regex metricNameRegex = null,
            bool foldersOnNamespacesLevel = false,
            bool downloadDefaultMetricConfig = false,
            int maxFileNameProducedLength = 256);

        /// <summary>
        /// Modify local metric configuration json files by replacing AccountName.
        /// </summary>
        /// <param name="sourceFolder">folder in which config files locate.</param>
        /// <param name="monitoringAccount">Monitoring account</param>
        /// <param name="replaceAccountNameWith">account name to replace original account with in local config files.</param>
        /// <param name="metricNameRegex">metric name regex.</param>
        /// <returns> OperationStatus.</returns>
        Task<OperationStatus> ReplaceAccountNameInMetricConfigurationFilesAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            string replaceAccountNameWith,
            Regex metricNameRegex = null);

        /// <summary>
        /// Modify local metric configuration json files by replacing Namespace.
        /// </summary>
        /// <param name="sourceFolder">folder in which config files locate.</param>
        /// <param name="monitoringAccount">Monitoring account</param>
        /// <param name="replaceNamespaceWith">namespace to replace original namespace with in local config files.</param>
        /// <param name="metricNameRegex">metric name regex.</param>
        /// <returns> OperationStatus.</returns>
        Task<OperationStatus> ReplaceNamespaceInMetricConfigurationFilesAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            string replaceNamespaceWith,
            Regex metricNameRegex = null);

        /// <summary>
        /// Upload all metric configurations from the given folder to the same monitoring account.
        /// </summary>
        /// <param name="sourceFolder">folder in which config files locate.</param>
        /// <param name="monitoringAccount">Monitoring account</param>
        /// <param name="force">indicate if true, skip version check for metric config against existing metric config version.</param>
        /// <returns> OperationStatus.</returns>
        Task<OperationStatus> UploadMetricConfigurationAsync(
            string sourceFolder,
            IMonitoringAccount monitoringAccount,
            bool force = false);

        /// <summary>
        /// Apply metric configuration from local template file to many metrics under one monitoring account.
        /// </summary>
        /// <param name="templateFilePath">absolute path of template metric configuration json file.</param>
        /// <param name="monitoringAccount">Monitoring account</param>
        /// <param name="metricNamespace">metric namespace</param>
        /// <param name="metricName">metric name</param>
        /// <param name="metricNameRegex">metric name regex</param>
        /// <param name="force">indicate if true, will overwrite existing config on server and skip version check.</param>
        /// <param name="whatIf">indicate if true, show the template config without actually uploading the config</param>
        /// <returns> OperationStatus.</returns>
        Task<OperationStatus> ApplyTemplateMetricConfigurationAsync(
            string templateFilePath,
            IMonitoringAccount monitoringAccount,
            string metricNamespace = null,
            string metricName = null,
            Regex metricNameRegex = null,
            bool force = false,
            bool whatIf = false);

        /// <summary>
        /// Synchronizes all metrics configurations only asynchronously across mirror accounts.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="skipVersionCheck">if set to <c>true</c> [skip version check].</param>
        /// <returns>A list of <see cref="ConfigurationUpdateResultList"/>.</returns>
        Task<IReadOnlyList<ConfigurationUpdateResultList>> SyncAllMetricsAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace = null,
            bool skipVersionCheck = false);

        /// <summary>
        /// Synchronizes the metric configuration only across mirror accounts asynchronously.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="skipVersionCheck">if set to <c>true</c> [skip version check].</param>
        /// <returns>An instance of <see cref="ConfigurationUpdateResultList"/>.</returns>
        Task<ConfigurationUpdateResultList> SyncMetricConfigurationAsync(
            IMonitoringAccount monitoringAccount,
            string metricNamespace,
            string metricName,
            bool skipVersionCheck = false);
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MonitorReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Monitors
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Threading.Tasks;

    using Microsoft.Cloud.Metrics.Client.Metrics;
    using Microsoft.Cloud.Metrics.Client.Utility;
    using Microsoft.Online.Metrics.Serialization;
    using Microsoft.Online.Metrics.Serialization.Configuration;
    using Microsoft.Online.Metrics.Serialization.Monitor;

    using Newtonsoft.Json;

    /// <summary>
    /// The class to query monitor health status.
    /// </summary>
    public sealed class MonitorReader : IMonitorReader
    {
        /// <summary>
        /// The number of attempts.
        /// </summary>
        public const int NumAttempts = 3;

#pragma warning disable SA1401 // Fields must be private
        /// <summary>
        /// The relative URL for health controller
        /// </summary>
        public readonly string HealthRelativeUrl;

        /// <summary>
        /// The V2 relative URL for metrics configuration
        /// </summary>
        public readonly string ConfigRelativeUrlV2;
#pragma warning restore SA1401 // Fields must be private

        /// <summary>
        /// The status sampling type.
        /// </summary>
        private static readonly SamplingType StatusSamplingType = new SamplingType("Status");

        /// <summary>
        /// The HTTP client instance.
        /// </summary>
        private readonly HttpClient httpClient;

        /// <summary>
        /// The connection information.
        /// </summary>
        private readonly ConnectionInfo connectionInfo;

        /// <summary>
        /// Initializes a new instance of the <see cref="MonitorReader"/> class.
        /// </summary>
        /// <param name="connectionInfo">The connection information.</param>
        public MonitorReader(ConnectionInfo connectionInfo)
        {
            if (connectionInfo == null)
            {
                throw new ArgumentNullException(nameof(connectionInfo));
            }

            this.connectionInfo = connectionInfo;
            this.HealthRelativeUrl = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.HealthRelativeUrl);
            this.ConfigRelativeUrlV2 = this.connectionInfo.GetAuthRelativeUrl(MetricsServerRelativeUris.ConfigRelativeUrlV2);
            this.httpClient = HttpClientHelper.CreateHttpClientWithAuthInfo(connectionInfo);

            this.GetResponseAsStringDelegate = HttpClientHelper.GetResponse;
        }

        /// <summary>
        /// Gets or sets the delegate to get web response as string.
        /// </summary>
        /// <remarks>For unit testing.</remarks>
        internal Func<Uri, HttpMethod, HttpClient, string, string, object, string, string, Guid?, byte, int, Task<Tuple<string, HttpResponseMessage>>> GetResponseAsStringDelegate { get; set; }

        /// <summary>
        /// Gets the monitors for the given <paramref name="metricIdentifier"/>.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <returns>The monitors for the given <paramref name="metricIdentifier"/>.</returns>
        public async Task<IReadOnlyList<MonitorIdentifier>> GetMonitorsAsync(MetricIdentifier metricIdentifier)
        {
            metricIdentifier.Validate();

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}/monitorIDs",
                this.connectionInfo.GetEndpoint(metricIdentifier.MonitoringAccount),
                this.ConfigRelativeUrlV2,
                metricIdentifier.MonitoringAccount,
                SpecialCharsHelper.EscapeTwice(metricIdentifier.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(metricIdentifier.MetricName));

            var response = await this.GetResponseAsStringDelegate(
                new Uri(url),
                HttpMethod.Get,
                this.httpClient,
                metricIdentifier.MonitoringAccount,
                this.ConfigRelativeUrlV2,
                null,
                string.Empty,
                null,
                null,
                MetricQueryResponseDeserializer.CurrentVersion,
                NumAttempts).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<MonitorIdentifier[]>(response.Item1);
        }

        /// <summary>
        /// Gets the monitor IDs for the given monitoring account, optionally with the metric namespace.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <returns>
        /// The monitor IDs for the given monitoring account, optionally with the metric namespace.
        /// </returns>
        public async Task<IReadOnlyList<MonitorIdentifier>> GetMonitorsAsync(string monitoringAccount, string metricNamespace = null)
        {
            if (string.IsNullOrWhiteSpace(monitoringAccount))
            {
                throw new ArgumentException("monitoringAccount is null or empty.", monitoringAccount);
            }

            string namespaceSegments = string.Empty;
            if (!string.IsNullOrWhiteSpace(metricNamespace))
            {
                namespaceSegments = string.Format("metricNamespace/{0}", SpecialCharsHelper.EscapeTwice(metricNamespace));
            }

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/{3}/monitorIDs",
                this.connectionInfo.GetEndpoint(monitoringAccount),
                this.ConfigRelativeUrlV2,
                monitoringAccount,
                namespaceSegments);

            var response = await this.GetResponseAsStringDelegate(
                new Uri(url),
                HttpMethod.Get,
                this.httpClient,
                monitoringAccount,
                this.ConfigRelativeUrlV2,
                null,
                string.Empty,
                null,
                null,
                MetricQueryResponseDeserializer.CurrentVersion,
                NumAttempts).ConfigureAwait(false);

            return JsonConvert.DeserializeObject<MonitorIdentifier[]>(response.Item1);
        }

        /// <summary>
        /// Gets the current heath status asynchronous.
        /// Deprecated due to wrong spelling ("Heath" instead of "Health") Exists for backward compatibility
        /// </summary>
        /// <param name="monitorInstanceDefinition">The monitor instance definition.</param>
        /// <returns>
        /// Monitor health status.
        /// </returns>
        public async Task<IMonitorHealthStatus> GetCurrentHeathStatusAsync(TimeSeriesDefinition<MonitorIdentifier> monitorInstanceDefinition)
        {
            return await this.GetCurrentHealthStatusAsync(monitorInstanceDefinition).ConfigureAwait(false);
        }

        /// <summary>
        /// Gets the current monitor health status.
        /// </summary>
        /// <param name="monitorInstanceDefinition">The monitor instance definition.</param>
        /// <returns>
        /// The current monitor health status.
        /// </returns>
        public async Task<IMonitorHealthStatus> GetCurrentHealthStatusAsync(TimeSeriesDefinition<MonitorIdentifier> monitorInstanceDefinition)
        {
            if (monitorInstanceDefinition == null)
            {
                throw new ArgumentNullException(nameof(monitorInstanceDefinition));
            }

            var statuses = await this.GetMultipleCurrentHeathStatusesAsync(monitorInstanceDefinition).ConfigureAwait(false);
            return statuses.First().Value;
        }

        /// <summary>
        /// Batched API to get the current monitor health statuses.
        /// </summary>
        /// <param name="monitorInstanceDefinitions">The monitor instance definitions.</param>
        /// <returns>
        /// The current monitor health statuses.
        /// </returns>
        public Task<IReadOnlyList<KeyValuePair<TimeSeriesDefinition<MonitorIdentifier>, IMonitorHealthStatus>>> GetMultipleCurrentHeathStatusesAsync(
            params TimeSeriesDefinition<MonitorIdentifier>[] monitorInstanceDefinitions)
        {
            return this.GetMultipleCurrentHeathStatusesAsync(monitorInstanceDefinitions.AsEnumerable());
        }

        /// <summary>
        /// Batched API to get the current monitor health statuses.
        /// </summary>
        /// <param name="monitorInstanceDefinitions">The monitor instance definitions.</param>
        /// <returns>
        /// The current monitor health statuses.
        /// </returns>
        public async Task<IReadOnlyList<KeyValuePair<TimeSeriesDefinition<MonitorIdentifier>, IMonitorHealthStatus>>> GetMultipleCurrentHeathStatusesAsync(
            IEnumerable<TimeSeriesDefinition<MonitorIdentifier>> monitorInstanceDefinitions)
        {
            if (monitorInstanceDefinitions == null)
            {
                throw new ArgumentNullException("monitorInstanceDefinitions");
            }

            var definitionList = monitorInstanceDefinitions.ToList();

            if (definitionList.Count == 0)
            {
                throw new ArgumentException("The count of 'monitorInstanceDefinitions' is 0.");
            }

            if (definitionList.Any(d => d == null))
            {
                throw new ArgumentException("At least one of monitorInstanceDefinitions are null.");
            }

            var monitorIdentifier = definitionList[0].Id;
            var dimensionCombinationList = new List<Dictionary<string, string>>(definitionList.Count);

            foreach (var definition in definitionList)
            {
                if (!definition.Id.Equals(monitorIdentifier))
                {
                    throw new MetricsClientException("All the time series definitions must have the same monitor identifier.");
                }

                var dict = new Dictionary<string, string>();
                if (definition.DimensionCombination != null)
                {
                    foreach (var kvp in definition.DimensionCombination)
                    {
                        dict[kvp.Key] = kvp.Value;
                    }
                }

                dimensionCombinationList.Add(dict);
            }

            string operation = $"{this.HealthRelativeUrl}/batchedRead";

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}/monitorId/{5}",
                this.connectionInfo.GetEndpoint(monitorIdentifier.MetricIdentifier.MonitoringAccount),
                operation,
                monitorIdentifier.MetricIdentifier.MonitoringAccount,
                SpecialCharsHelper.EscapeTwice(monitorIdentifier.MetricIdentifier.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(monitorIdentifier.MetricIdentifier.MetricName),
                SpecialCharsHelper.EscapeTwice(monitorIdentifier.MonitorId));

            var response = await this.GetResponseAsStringDelegate(
                new Uri(url),
                HttpMethod.Post,
                this.httpClient,
                monitorIdentifier.MetricIdentifier.MonitoringAccount,
                operation,
                dimensionCombinationList,
                string.Empty,
                null,
                null,
                MetricQueryResponseDeserializer.CurrentVersion,
                NumAttempts).ConfigureAwait(false);

            var deserializeObject = JsonConvert.DeserializeObject<List<MonitorHealthStatus>>(response.Item1);
            var results = new KeyValuePair<TimeSeriesDefinition<MonitorIdentifier>, IMonitorHealthStatus>[deserializeObject.Count];

            for (int i = 0; i < deserializeObject.Count; ++i)
            {
                var status = new KeyValuePair<TimeSeriesDefinition<MonitorIdentifier>, IMonitorHealthStatus>(definitionList[i], deserializeObject[i]);

                results[i] = status;
            }

            return results;
        }

        /// <summary>
        /// Gets the monitor history.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="monitorInstanceDefinition">The monitor instance definition.</param>
        /// <returns>
        /// The monitor health history for each minute in the provided time range.
        /// true means healthy, false means unhealthy, and null means that the monitor didn't report a health status for that minute.
        /// </returns>
        [Obsolete("We are going to retire this. Please use GetBatchWatchdogHealthHistory in Health SDK instead.")]
        public async Task<TimeSeries<MonitorIdentifier, bool?>> GetMonitorHistoryAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            TimeSeriesDefinition<MonitorIdentifier> monitorInstanceDefinition)
        {
            if (monitorInstanceDefinition == null)
            {
                throw new ArgumentNullException("monitorInstanceDefinition");
            }

            if (startTimeUtc > endTimeUtc)
            {
                throw new ArgumentException(string.Format("startTimeUtc [{0}] must be <= endTimeUtc [{1}]", startTimeUtc, endTimeUtc));
            }

            startTimeUtc = new DateTime(startTimeUtc.Ticks / TimeSpan.TicksPerMinute * TimeSpan.TicksPerMinute);
            endTimeUtc = new DateTime(endTimeUtc.Ticks / TimeSpan.TicksPerMinute * TimeSpan.TicksPerMinute);

            string dimensionsFlattened = null;

            if (monitorInstanceDefinition.DimensionCombination != null)
            {
                dimensionsFlattened = string.Join(
                    "/",
                    monitorInstanceDefinition.DimensionCombination.Select(
                        d => string.Join("/", SpecialCharsHelper.EscapeTwice(d.Key), SpecialCharsHelper.EscapeTwice(d.Value))));
            }

            string operation = $"{this.HealthRelativeUrl}/history";

            string url = string.Format(
                "{0}{1}/monitoringAccount/{2}/metricNamespace/{3}/metric/{4}/monitorId/{5}/from/{6}/to/{7}{8}",
                this.connectionInfo.GetEndpoint(monitorInstanceDefinition.Id.MetricIdentifier.MonitoringAccount),
                operation,
                monitorInstanceDefinition.Id.MetricIdentifier.MonitoringAccount,
                SpecialCharsHelper.EscapeTwice(monitorInstanceDefinition.Id.MetricIdentifier.MetricNamespace),
                SpecialCharsHelper.EscapeTwice(monitorInstanceDefinition.Id.MetricIdentifier.MetricName),
                SpecialCharsHelper.EscapeTwice(monitorInstanceDefinition.Id.MonitorId),
                UnixEpochHelper.GetMillis(startTimeUtc),
                UnixEpochHelper.GetMillis(endTimeUtc),
                dimensionsFlattened != null ? "/" + dimensionsFlattened : string.Empty);

            var response = await this.GetResponseAsStringDelegate(
                new Uri(url),
                HttpMethod.Get,
                this.httpClient,
                monitorInstanceDefinition.Id.MetricIdentifier.MonitoringAccount,
                operation,
                null,
                string.Empty,
                null,
                null,
                MetricQueryResponseDeserializer.CurrentVersion,
                NumAttempts).ConfigureAwait(false);

            var values = JsonConvert.DeserializeObject<List<bool?>>(response.Item1);
            return new TimeSeries<MonitorIdentifier, bool?>(startTimeUtc, endTimeUtc, SerializationConstants.DefaultSeriesResolutionInMinutes, monitorInstanceDefinition, new List<List<bool?>> { values }, TimeSeriesErrorCode.Success);
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IPermissionV2.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// This object represents one entity and their level of access to an MDM Account.
    /// </summary>
    public interface IPermissionV2
    {
        /// <summary>
        /// The identity to grant permission.
        /// </summary>
        string Identity { get; }

        /// <summary>
        /// Gets the description of the permission.
        /// </summary>
        /// <remarks>Description is always null for <see cref="SecurityGroupV2"/> and <see cref="UserPermissionV2"/> class.</remarks>
        [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
        string Description { get; }

        /// <summary>
        /// The level of access to be granted to this identity.
        /// </summary>
        RoleConfiguration RoleConfiguration { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IMonitorReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Monitors
{
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;

    using Microsoft.Online.Metrics.Serialization.Configuration;
    using Microsoft.Online.Metrics.Serialization.Monitor;

    /// <summary>
    /// The interface to query monitor health status.
    /// </summary>
    public interface IMonitorReader
    {
        /// <summary>
        /// Gets the monitors for the given <paramref name="metricIdentifier"/>.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <returns>The monitors for the given <paramref name="metricIdentifier"/>.</returns>
        Task<IReadOnlyList<MonitorIdentifier>> GetMonitorsAsync(MetricIdentifier metricIdentifier);

        /// <summary>
        /// Gets the monitor IDs for the given monitoring account, optionally with the metric namespace.
        /// </summary>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <returns>
        /// The monitor IDs for the given monitoring account, optionally with the metric namespace.
        /// </returns>
        Task<IReadOnlyList<MonitorIdentifier>> GetMonitorsAsync(string monitoringAccount, string metricNamespace = null);

        /// <summary>
        /// Gets the current heath status asynchronous.
        /// </summary>
        /// <param name="monitorInstanceDefinition">The monitor instance definition.</param>
        /// <returns>Monitor health status.</returns>
        [System.Obsolete("Deprecated, please use GetCurrentHealthStatusAsync (notice fixed spelling) instead.")]
        Task<IMonitorHealthStatus> GetCurrentHeathStatusAsync(TimeSeriesDefinition<MonitorIdentifier> monitorInstanceDefinition);

        /// <summary>
        /// Gets the current monitor health status.
        /// </summary>
        /// <param name="monitorInstanceDefinition">The monitor instance definition.</param>
        /// <returns>
        /// The current monitor health status.
        /// </returns>
        Task<IMonitorHealthStatus> GetCurrentHealthStatusAsync(TimeSeriesDefinition<MonitorIdentifier> monitorInstanceDefinition);

        /// <summary>
        /// Batched API to get the current monitor health statuses.
        /// </summary>
        /// <param name="monitorInstanceDefinitions">The monitor instance definitions.</param>
        /// <returns>
        /// The current monitor health statuses.
        /// </returns>
        Task<IReadOnlyList<KeyValuePair<TimeSeriesDefinition<MonitorIdentifier>, IMonitorHealthStatus>>> GetMultipleCurrentHeathStatusesAsync(
            params TimeSeriesDefinition<MonitorIdentifier>[] monitorInstanceDefinitions);

        /// <summary>
        /// Batched API to get the current monitor health statuses.
        /// </summary>
        /// <param name="monitorInstanceDefinitions">The monitor instance definitions.</param>
        /// <returns>
        /// The current monitor health statuses.
        /// </returns>
        Task<IReadOnlyList<KeyValuePair<TimeSeriesDefinition<MonitorIdentifier>, IMonitorHealthStatus>>> GetMultipleCurrentHeathStatusesAsync(
            IEnumerable<TimeSeriesDefinition<MonitorIdentifier>> monitorInstanceDefinitions);

        /// <summary>
        /// Gets the monitor history.
        /// </summary>
        /// <param name="startTimeUtc">The start time UTC.</param>
        /// <param name="endTimeUtc">The end time UTC.</param>
        /// <param name="monitorInstanceDefinition">The monitor instance definition.</param>
        /// <returns>
        /// The monitor health history for each minute in the provided time range.
        /// true means healthy, false means unhealthy, and null means that the monitor didn't report a health status for that minute.
        /// </returns>
        [Obsolete("We are going to retire this. Please use GetBatchWatchdogHealthHistory in Health SDK instead.")]
        Task<TimeSeries<MonitorIdentifier, bool?>> GetMonitorHistoryAsync(
            DateTime startTimeUtc,
            DateTime endTimeUtc,
            TimeSeriesDefinition<MonitorIdentifier> monitorInstanceDefinition);
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="FilteredTimeSeries.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System.Collections.Generic;
    using System.Text;
    using Metrics;

    using Microsoft.Online.Metrics.Serialization.Configuration;

    /// <summary>
    /// Represents a single time series that was selected and is being returned as an item in <see cref="FilteredTimeSeriesQueryResponse"/>.
    /// </summary>
    public sealed class FilteredTimeSeries : IFilteredTimeSeries, IQueryResultV3
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="FilteredTimeSeries" /> class.
        /// Create a single time series.
        /// </summary>
        /// <param name="metricIdentifier">The metric identifier.</param>
        /// <param name="dimensionList">The list of dimensions.</param>
        /// <param name="evaluatedResult">The evaluated result.</param>
        /// <param name="seriesValues">The time series values, only included if specified in the query.</param>
        internal FilteredTimeSeries(
            MetricIdentifier metricIdentifier,
            IReadOnlyList<KeyValuePair<string, string>> dimensionList,
            double evaluatedResult,
            IReadOnlyList<KeyValuePair<SamplingType, double[]>> seriesValues)
        {
            this.MetricIdentifier = metricIdentifier;
            this.DimensionList = dimensionList;
            this.EvaluatedResult = evaluatedResult;
            this.TimeSeriesValues = seriesValues;
        }

        /// <summary>
        /// Gets the metric identifier.
        /// </summary>
        public MetricIdentifier MetricIdentifier { get; }

        /// <summary>
        /// Set of valid dimension name-value pairs that meet the query condition.
        /// </summary>
        public IReadOnlyList<KeyValuePair<string, string>> DimensionList { get; }

        /// <summary>
        /// Gets the evaluated value for this time series that meets the condition set in the query (provided for evidence and/or sorting).
        /// </summary>
        public double EvaluatedResult { get; }

        /// <summary>
        /// Gets the full collection time series values for the query interval. It should be null if
        /// the query did not request the full collection of values to be returned.
        /// </summary>
        public IReadOnlyList<KeyValuePair<SamplingType, double[]>> TimeSeriesValues { get; }

        /// <summary>
        /// Gets the time series values for the requested sampling type.
        /// </summary>
        /// <param name="samplingType">The sampling type requested.</param>
        /// <returns>The array of datapoints for the requested sampling type/</returns>
        /// <exception cref="System.Collections.Generic.KeyNotFoundException">Thrown if the sampling type was not included in the response.</exception>
        /// <remarks>
        /// double.NaN is the sentinel used to indicate there is no metric value.
        /// </remarks>
        public double[] GetTimeSeriesValues(SamplingType samplingType)
        {
            for (var i = 0; i < this.TimeSeriesValues.Count; ++i)
            {
                if (samplingType.Equals(this.TimeSeriesValues[i].Key))
                {
                    return this.TimeSeriesValues[i].Value;
                }
            }

            throw new KeyNotFoundException($"Sampling type {samplingType} not found in the query result.");
        }

        /// <summary>
        /// Returns a <see cref="string" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="string" /> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine($"EvaluatedResult: {this.EvaluatedResult}");
            sb.Append("Dimensions:");
            foreach (var pair in this.DimensionList)
            {
                sb.Append($"{pair.Key}: {pair.Value};");
            }

            sb.AppendLine();
            if (this.TimeSeriesValues != null && this.TimeSeriesValues.Count > 0)
            {
                sb.Append("[");
                for (int i = 0; i < this.TimeSeriesValues.Count; i++)
                {
                    sb.Append("[");
                    sb.Append($"{this.TimeSeriesValues[i].Key}, ");
                    sb.Append(string.Join(", ", this.TimeSeriesValues[i].Value));
                    sb.AppendLine("]");
                }

                sb.AppendLine("]");
            }

            return sb.ToString();
        }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MonitoringAccountAcls.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Threading.Tasks;

    using Newtonsoft.Json;
    using Utility;

    /// <summary>
    /// Represents a list of ACLs associated with a monitoring account.
    /// </summary>
    /// <remarks>
    /// This does not include AP PKI as the cert is automatically generated and the ACL itself is not needed client side.
    /// </remarks>
    internal sealed class MonitoringAccountAcls : IMonitoringAccountAcls
    {
        /// <summary>
        /// Gets or sets the thumbprints.
        /// </summary>
        [JsonProperty(PropertyName = "tps")]
        public List<string> Thumbprints { get; set; }

        /// <summary>
        /// Gets or sets the dSMS acls.
        /// </summary>
        [JsonProperty(PropertyName = "dacls")]
        public List<string> DsmsAcls { get; set; }

        /// <summary>
        /// Gets or sets the KeyVault acls.
        /// </summary>
        [JsonProperty(PropertyName = "kvacls")]
        public List<string> KeyVaultAcls { get; set; }

        /// <summary>
        /// Gets the ACLs definied for the specified monitoring account
        /// </summary>
        /// <param name="accountName">Name of the account.</param>
        /// <param name="targetStampEndpoint">If not a production account, allows the target stamp to be overridden.  In most cases, allowing to default is appropriate.</param>
        /// <param name="includeReadOnly">True if the set should include those with read only access, otherwise false to return those with higher rights.</param>
        /// <returns>Acls for monitoring account.</returns>
        /// <exception cref="System.ArgumentNullException">accountName</exception>
        public static async Task<IMonitoringAccountAcls> GetAcls(string accountName, string targetStampEndpoint = "https://global.metrics.nsatc.net", bool includeReadOnly = true)
        {
            if (string.IsNullOrWhiteSpace(accountName))
            {
                throw new ArgumentNullException(nameof(accountName));
            }

            var client = HttpClientHelper.CreateHttpClient(TimeSpan.FromMinutes(1));
            var requestUri = $"{targetStampEndpoint}/public/monitoringAccount/{accountName}/acls?includeReadOnly={includeReadOnly}";
            var result = await HttpClientHelper.GetResponse(new Uri(requestUri), HttpMethod.Get, client, null, null).ConfigureAwait(false);
            return JsonConvert.DeserializeObject<MonitoringAccountAcls>(result.Item1);
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="IMetricBuilder.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;
    using System.Collections.Generic;
    using System.IO;

    /// <summary>
    /// The interface used by deserializer to abstract the way how deserialized objects are created.
    /// It is up to the user of the deserializer to implement type to which data are deserialized, use object pools or not etc.
    /// </summary>
    /// <typeparam name="TMetadata">Type of metadata to use for creation of metric data objects.</typeparam>
    public interface IMetricBuilder<TMetadata>
        where TMetadata : IMetricMetadata
    {
        /// <summary>
        /// Sets the packet serialization version.
        /// </summary>
        /// <param name="serializationVersion">Serialization version.</param>
        void SetSerializationVersion(ushort serializationVersion);

        /// <summary>
        /// Creates the custom object representing metric metadata. Note that the same metadata object can be shared
        /// by many metric instances.
        /// </summary>
        /// <param name="metricNamespace">Namespace of the metric.</param>
        /// <param name="metricName">Name of the metric.</param>
        /// <param name="dimensionNames">Names of the metric dimensions.</param>
        /// <returns>The metric metadata representing the given parameters.</returns>
        TMetadata CreateMetadata(string metricNamespace, string metricName, IEnumerable<string> dimensionNames);

        /// <summary>
        /// Signals to the builder that it should get ready to start filling up data for a new metric.
        /// </summary>
        void BeginMetricCreation();

        /// <summary>
        /// Assigns metadata to the metric being built.
        /// </summary>
        /// <param name="metadata">Metric metadata.</param>
        void AssignMetadata(TMetadata metadata);

        /// <summary>
        /// Assigns a monitoring account to the metric being built. This is only called if the aggregated metric data was serialized
        /// with a version prior to v3. For v3 and above the monitoring account should be captured on the request itself, as part of the
        /// URL path or as a query parameter.
        /// </summary>
        /// <param name="value">
        /// Monitoring account associated with the metric.
        /// </param>
        /// <remarks>
        /// If we ever make this interface public we should check if there are no more clients submitting data prior to v3 and remove
        /// this method from the interface to avoid confusion from the users.
        /// </remarks>
        void AssignMonitoringAccount(string value);

        /// <summary>
        /// Assigns a namespace to the metric being built.
        /// </summary>
        /// <param name="value">Namespace associated with the metric.</param>
        void AssignNamespace(string value);

        /// <summary>
        /// Assigns time (UTC) to the metric being built.
        /// </summary>
        /// <param name="value">Time of the metric.</param>
        void AssignTimeUtc(DateTime value);

        /// <summary>
        /// Adds the value of a single dimension to the metric being built. The dimension values are passed in the same
        /// order as the corresponding dimensions on the metric metadata.
        /// </summary>
        /// <param name="value">Value of one dimension of the metric.</param>
        void AddDimensionValue(string value);

        /// <summary>
        /// Assigns the sampling types of the metric being built.
        /// </summary>
        /// <param name="value">Available sampling types of the metric.</param>
        void AssignSamplingTypes(SamplingTypes value);

        /// <summary>
        /// Assigns the minimum value of the metric being built.
        /// </summary>
        /// <param name="value">Minimum value of the metric.</param>
        void AssignMin(ulong value);

        /// <summary>
        /// Assigns the maximum value of the metric being built.
        /// </summary>
        /// <param name="value">Maximum value of the metric.</param>
        void AssignMax(ulong value);

        /// <summary>
        /// Assigns the sum value of the metric being built.
        /// </summary>
        /// <param name="value">Sum value of the metric.</param>
        void AssignSum(ulong value);

        /// <summary>
        /// Assigns the sum of square differences from mean value of the metric being built.
        /// </summary>
        /// <param name="value">Sum of square differences from mean of the value of the metric.</param>
        void AssignSumOfSquareDiffFromMean(double value);

        /// <summary>
        /// Assigns the count (i.e.: how many times it was logged) value of the metric being built.
        /// </summary>
        /// <param name="value">Count value of the metric.</param>
        void AssignCount(uint value);

        /// <summary>
        /// Assigns a histogram to the metric being built.
        /// </summary>
        /// <param name="value">Histogram of the metric.</param>
        void AssignHistogram(IReadOnlyList<KeyValuePair<ulong, uint>> value);

        /// <summary>
        /// Assigns hyperloglogsketches to the metric being built.
        /// </summary>
        /// <param name="reader">Stream containing the data.</param>
        /// <param name="length">Length of data to read.</param>
        void AssignHyperLogLogSketch(BinaryReader reader, int length);

        /// <summary>
        /// Signals to the builder that the creation of the current metric was completed.
        /// </summary>
        void EndMetricCreation();
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LocalMetricReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using Etw;
    using Logging;

    /// <summary>
    /// The class for consumption of locally aggregated metrics or local raw metrics.
    /// </summary>
    public sealed unsafe class LocalMetricReader : ILocalMetricReader
    {
        /// <summary>
        /// Prefix added to ETW sessions that are using the configuration of a collector.
        /// </summary>
        private const string EtwSessionsPrefix = "LocalMetricReader-";

        /// <summary>
        /// Custom log id to be used in the log statements.
        /// </summary>
        private static readonly object LogId = Logger.CreateCustomLogId("LocalMetricReader");

        /// <summary>
        /// The aggregated metrics provider unique identifier.
        /// </summary>
        private static readonly Guid AggregatedMetricsProviderGuid = new Guid("{2F23A2A9-0DE7-4CB4-A778-FBDF5C1E7372}");

        /// <summary>
        /// The raw metrics etw provider unique identifier.
        /// </summary>
        private static readonly Guid RawMetricsEtwProviderGuid = new Guid("{EDC24920-E004-40F6-A8E1-0E6E48F39D84}");

        /// <summary>
        /// Gets or sets a value indicating whether [enable verbose logging].
        /// </summary>
        public bool EnableVerboseLogging { get; set; }

        /// <summary>
        /// Reads the local raw metrics.
        /// </summary>
        /// <param name="metricProducedAction">The action to execute when metric available.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="etlFileName">The name of the etw file from when read data. If null, realtime session will be used.</param>
        /// <returns>
        /// An awaitable <see cref="Task" />.
        /// </returns>
        public Task ReadLocalRawMetricsAsync(
            Action<ILocalRawMetric> metricProducedAction,
            CancellationToken cancellationToken,
            string etlFileName = null)
        {
            if (metricProducedAction == null)
            {
                throw new ArgumentNullException("metricProducedAction");
            }

            if (cancellationToken == null)
            {
                throw new ArgumentNullException("cancellationToken");
            }

            if (!this.EnableVerboseLogging)
            {
                Logger.SetMaxLogLevel(LoggerLevel.Error);
            }

            ActiveCollector activeCollector = null;
            Task task = Task.Factory.StartNew(
                () =>
                    {
                        try
                        {
                            Task.Factory.StartNew(
                                () =>
                                SetupRawMetricListener(
                                    RawMetricsEtwProviderGuid,
                                    etlFileName,
                                    metricProducedAction,
                                    cancellationToken,
                                    out activeCollector),
                                TaskCreationOptions.LongRunning);

                            cancellationToken.WaitHandle.WaitOne();
                        }
                        finally
                        {
                            StopEtwSession(activeCollector);
                        }
                    },
                TaskCreationOptions.LongRunning);

            Console.CancelKeyPress += (sender, args) => StopEtwSession(activeCollector);

            return task;
        }

        /// <summary>
        /// Reads the locally aggregated metrics.
        /// </summary>
        /// <param name="metricProducedAction">The action to execute when metric available.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="etlFileName">The name of the etw file from when read data. If null, realtime session will be used.</param>
        /// <returns>
        /// An awaitable <see cref="Task" />.
        /// </returns>
        public Task ReadLocalAggregatedMetricsAsync(
            Action<ILocalAggregatedMetric> metricProducedAction,
            CancellationToken cancellationToken,
            string etlFileName = null)
        {
            if (metricProducedAction == null)
            {
                throw new ArgumentNullException("metricProducedAction");
            }

            if (cancellationToken == null)
            {
                throw new ArgumentNullException("cancellationToken");
            }

            if (!this.EnableVerboseLogging)
            {
                Logger.SetMaxLogLevel(LoggerLevel.Error);
            }

            ActiveCollector activeCollector = null;

            Task task = Task.Factory.StartNew(
                () =>
                    {
                        try
                        {
                            Task.Factory.StartNew(
                                () =>
                                SetupAggregatedMetricListener(
                                    AggregatedMetricsProviderGuid,
                                    etlFileName,
                                    metricProducedAction,
                                    cancellationToken,
                                    out activeCollector),
                                TaskCreationOptions.LongRunning);

                            cancellationToken.WaitHandle.WaitOne();
                        }
                        finally
                        {
                            StopEtwSession(activeCollector);
                        }
                    },
                TaskCreationOptions.LongRunning);

            Console.CancelKeyPress += (sender, args) => StopEtwSession(activeCollector);

            return task;
        }

        /// <summary>
        /// Stops the etw session.
        /// </summary>
        /// <param name="activeCollector">The active collector.</param>
        private static void StopEtwSession(ActiveCollector activeCollector)
        {
            if (activeCollector != null)
            {
                ActiveCollector.StopCollector(activeCollector.Name);
            }
        }

        /// <summary>
        /// Setups the raw metric listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlFileName">The name of the etw file from when read data. If null, realtime session will be used.</param>
        /// <param name="metricProducedAction">The when metric available.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="activeCollector">The active collector.</param>
        private static void SetupRawMetricListener(
            Guid providerGuid,
            string etlFileName,
            Action<ILocalRawMetric> metricProducedAction,
            CancellationToken cancellationToken,
            out ActiveCollector activeCollector)
        {
            var providers = new Dictionary<Guid, ProviderConfiguration>
                                {
                                    {
                                        providerGuid,
                                        new ProviderConfiguration(providerGuid, EtwTraceLevel.Verbose, 0, 0)
                                    }
                                };

            var etwSessionConfig = new CollectorConfiguration(EtwSessionsPrefix + "raw-")
                                       {
                                           SessionType = SessionType.Realtime,
                                           Providers = providers
                                       };

            activeCollector = new ActiveCollector(etwSessionConfig.Name);
            activeCollector.StartCollector(etwSessionConfig);

            RawListener etwListener = null;
            try
            {
                etwListener = string.IsNullOrWhiteSpace(etlFileName)
                                  ? CreateRealTimeListener(providerGuid, etwSessionConfig.Name, metricProducedAction, cancellationToken)
                                  : CreateFileListener(providerGuid, etlFileName, metricProducedAction, cancellationToken);

                // TODO: Better to check providers periodically and retry several times.
                if (!ActiveCollector.TryUpdateProviders(etwSessionConfig))
                {
                    Logger.Log(LoggerLevel.Error, LogId, "Main", "Failed to update ETW providers. Terminating.");
                    return;
                }

                try
                {
                    etwListener.Process();
                }
                finally
                {
                    Logger.Log(
                        cancellationToken.IsCancellationRequested ? LoggerLevel.Info : LoggerLevel.Error,
                        LogId,
                        "SetupEtwDataPipeline",
                        "ETW Thread terminated unexpectedly, typically indicates that the ETW session was stopped.");
                }
            }
            finally
            {
                if (etwListener != null)
                {
                    etwListener.Dispose();
                }
            }
        }

        /// <summary>
        /// Setups the aggregated metric listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlFileName">The name of the etw file from when read data. If null, realtime session will be used.</param>
        /// <param name="metricProducedAction">The when metric available.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="activeCollector">The active collector.</param>
        private static void SetupAggregatedMetricListener(
            Guid providerGuid,
            string etlFileName,
            Action<ILocalAggregatedMetric> metricProducedAction,
            CancellationToken cancellationToken,
            out ActiveCollector activeCollector)
        {
            var providers = new Dictionary<Guid, ProviderConfiguration>
                                {
                                    {
                                        providerGuid,
                                        new ProviderConfiguration(providerGuid, EtwTraceLevel.Verbose, 0, 0)
                                    }
                                };

            var etwSessionConfig = new CollectorConfiguration(EtwSessionsPrefix + "aggregated-")
                                       {
                                           SessionType = SessionType.Realtime,
                                           Providers = providers
                                       };

            activeCollector = new ActiveCollector(etwSessionConfig.Name);
            activeCollector.StartCollector(etwSessionConfig);

            RawListener etwListener = null;
            try
            {
                etwListener = string.IsNullOrWhiteSpace(etlFileName)
                                  ? CreateRealTimeListener(providerGuid, etwSessionConfig.Name, metricProducedAction, 1, cancellationToken)
                                  : CreateFileListener(providerGuid, etlFileName, metricProducedAction, 1, cancellationToken);

                // TODO: Better to check providers periodically and retry several times.
                if (!ActiveCollector.TryUpdateProviders(etwSessionConfig))
                {
                    Logger.Log(LoggerLevel.Error, LogId, "Main", "Failed to update ETW providers. Terminating.");
                    return;
                }

                try
                {
                    etwListener.Process();
                }
                finally
                {
                    Logger.Log(
                        cancellationToken.IsCancellationRequested ? LoggerLevel.Info : LoggerLevel.Error,
                        LogId,
                        "SetupEtwDataPipeline",
                        "ETW Thread terminated unexpectedly, typically indicates that the ETW session was stopped.");
                }
            }
            finally
            {
                if (etwListener != null)
                {
                    etwListener.Dispose();
                }
            }
        }

        /// <summary>
        /// Creates the real time listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlSessionConfigName">Name of the etl session configuration.</param>
        /// <param name="metricProducedAction">The metric produced action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>An instance of <see cref="RawListener"/>.</returns>
        private static RawListener CreateRealTimeListener(
            Guid providerGuid,
            string etlSessionConfigName,
            Action<ILocalRawMetric> metricProducedAction,
            CancellationToken cancellationToken)
        {
            return RawListener.CreateRealTimeListener(
                etlSessionConfigName,
                eventRecord =>
                    {
                        if (eventRecord->EventHeader.ProviderId == providerGuid)
                        {
                            metricProducedAction(LocalRawMetric.ConvertToMetricData(eventRecord));
                        }
                    },
                eventTraceLog =>
                    {
                        Logger.Log(
                            LoggerLevel.Info,
                            LogId,
                            "CreateRealTimeListener",
                            "LocalRawMetric, cancelled = {0}",
                            cancellationToken.IsCancellationRequested);

                        return !cancellationToken.IsCancellationRequested;
                    });
        }

        /// <summary>
        /// Creates the file listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlFileName">Name of the etl file.</param>
        /// <param name="metricProducedAction">The metric produced action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>An instance of <see cref="RawListener"/>.</returns>
        private static RawListener CreateFileListener(
            Guid providerGuid,
            string etlFileName,
            Action<ILocalRawMetric> metricProducedAction,
            CancellationToken cancellationToken)
        {
            return RawListener.CreateEtlFileListener(
                new[] { etlFileName },
                eventRecord =>
                    {
                        if (eventRecord->EventHeader.ProviderId == providerGuid)
                        {
                            metricProducedAction(LocalRawMetric.ConvertToMetricData(eventRecord));
                        }
                    },
                eventTraceLog =>
                    {
                        Logger.Log(
                            LoggerLevel.Info,
                            LogId,
                            "CreateFileListener",
                            "LocalRawMetric, cancelled = {0}",
                            cancellationToken.IsCancellationRequested);

                        return !cancellationToken.IsCancellationRequested;
                    });
        }

        /// <summary>
        /// Creates the real time listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlSessionConfigName">Name of the etl session configuration.</param>
        /// <param name="metricProducedAction">The metric produced action.</param>
        /// <param name="eventIdFilter">The event identifier filter.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A <see cref="RawListener"/> instance.</returns>
        private static RawListener CreateRealTimeListener(
            Guid providerGuid,
            string etlSessionConfigName,
            Action<ILocalAggregatedMetric> metricProducedAction,
            int eventIdFilter,
            CancellationToken cancellationToken)
        {
            return RawListener.CreateRealTimeListener(
                etlSessionConfigName,
                eventRecord =>
                    {
                        if (eventRecord->EventHeader.ProviderId == providerGuid && eventRecord->EventHeader.Id == eventIdFilter)
                        {
                            metricProducedAction(LocalAggregatedMetric.ConvertToMetricData(eventRecord));
                        }
                    },
                eventTraceLog =>
                    {
                        Logger.Log(
                            LoggerLevel.Info,
                            LogId,
                            "CreateRealTimeListener",
                            "LocalAggregatedMetric, cancelled = {0}",
                            cancellationToken.IsCancellationRequested);

                        return !cancellationToken.IsCancellationRequested;
                    });
        }

        /// <summary>
        /// Creates the file listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlFileName">Name of the etl file.</param>
        /// <param name="metricProducedAction">The metric produced action.</param>
        /// <param name="eventIdFilter">The event identifier filter.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A <see cref="RawListener"/> instance.</returns>
        private static RawListener CreateFileListener(
            Guid providerGuid,
            string etlFileName,
            Action<ILocalAggregatedMetric> metricProducedAction,
            int eventIdFilter,
            CancellationToken cancellationToken)
        {
            return RawListener.CreateEtlFileListener(
                new[] { etlFileName },
                eventRecord =>
                    {
                        if (eventRecord->EventHeader.ProviderId == providerGuid && eventRecord->EventHeader.Id == eventIdFilter)
                        {
                            metricProducedAction(LocalAggregatedMetric.ConvertToMetricData(eventRecord));
                        }
                    },
                eventTraceLog =>
                    {
                        Logger.Log(
                            LoggerLevel.Info,
                            LogId,
                            "CreateFileListener",
                            "LocalAggregatedMetric, cancelled = {0}",
                            cancellationToken.IsCancellationRequested);

                        return !cancellationToken.IsCancellationRequested;
                    });
        }
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="FilteringConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using Newtonsoft.Json;

    /// <summary>
    /// Determine if the filtering is enabled or disabled for this preaggregate.
    /// </summary>
    public sealed class FilteringConfiguration : IFilteringConfiguration
    {
        /// <summary>
        /// IFilteringConfiguration where filtering is enabled.
        /// </summary>
        public static readonly IFilteringConfiguration FilteringEnabled = new FilteringConfiguration(true);

        /// <summary>
        /// IFilteringConfiguration where filtering is disabled.
        /// </summary>
        public static readonly IFilteringConfiguration FilteringDisabled = new FilteringConfiguration(false);

        /// <summary>
        /// Initializes a new instance of the <see cref="FilteringConfiguration"/> class.
        /// </summary>
        /// <param name="enabled">Whether or not the feature is enabled.</param>
        [JsonConstructor]
        internal FilteringConfiguration(bool enabled)
        {
            this.Enabled = enabled;
        }

        /// <summary>
        /// Determines if the filtering is enabled for this pre-aggregate based on configured pre-aggregate filters.
        /// </summary>
        public bool Enabled { get; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StampLocator.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.ThirdParty
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Cloud.Metrics.Client.Utility;

    using Newtonsoft.Json;

    /// <summary>
    /// Helper class to locate the stamp endpoint for Azure external customers' 3rd party accounts.
    /// </summary>
    public sealed class StampLocator : IStampLocator
    {
        private const string StampHostNameSuffix = ".metrics.nsatc.net";
        private const string FileNameForRegionStampMap = "RegionToGenevaMetricsStampMap.json";
        private static readonly Uri ThirdPartyRegionStampMapUrl = new Uri("https://stamplocator.metrics.nsatc.net/public/thirdPartyRegionStampMap");
        private readonly Uri stampLocatorUrl;
        private readonly string fullFilePathForRegionStampMap;
        private readonly HttpClient httpClient;
        private readonly TimeSpan refreshInternal = TimeSpan.FromHours(1);
        private readonly ActivityReporter activityReporter;
        private Timer timerToRefreshRegionStampMap;
        private Dictionary<string, string> regionStampMap;

        /// <summary>
        /// Initializes a new instance of the <see cref="StampLocator" /> class.
        /// </summary>
        /// <param name="fullFilePathForRegionStampMap">The full file path for region stamp map.</param>
        /// <param name="regionStampMap">The region stamp map.</param>
        /// <param name="httpClient">The HTTP client.</param>
        /// <param name="stampLocatorUrl">The stamp locator URL to retrieve the region stamp map.</param>
        /// <param name="activityReporter">The activity reporter.</param>
        /// <exception cref="MetricsClientException">The region stamp map failed to initialize or is empty.</exception>
        private StampLocator(string fullFilePathForRegionStampMap, Dictionary<string, string> regionStampMap, HttpClient httpClient, Uri stampLocatorUrl, ActivityReporter activityReporter)
        {
            this.fullFilePathForRegionStampMap = fullFilePathForRegionStampMap;
            this.regionStampMap = regionStampMap;
            this.httpClient = httpClient;
            this.stampLocatorUrl = stampLocatorUrl;
            this.activityReporter = activityReporter;
        }

        /// <summary>
        /// The call back to report StampLocator activities. Consumers are expected to emit metrics and logs.
        /// </summary>
        /// <param name="activity">The activity.</param>
        /// <param name="isError">if set to <c>true</c>, the activity is an error.</param>
        /// <param name="detail">The detail about the activity.</param>
        public delegate void ActivityReporter(StampLocatorActivity activity, bool isError, string detail);

        /// <summary>
        /// Creates an instance of <see cref="StampLocator"/> asynchronously.
        /// </summary>
        /// <param name="folderToCacheRegionStampMap">The folder to cache region stamp map.</param>
        /// <param name="activityReporter">The activity reporter. The string argument contains the error detail when the activity results in an error; otherwise it is null.</param>
        /// <returns>An instance of <see cref="StampLocator"/>.</returns>
        public static Task<IStampLocator> CreateInstanceAsync(string folderToCacheRegionStampMap, ActivityReporter activityReporter)
        {
            if (string.IsNullOrWhiteSpace(folderToCacheRegionStampMap))
            {
                throw new ArgumentException("The argument is null or empty", nameof(folderToCacheRegionStampMap));
            }

            if (activityReporter == null)
            {
                throw new ArgumentNullException(nameof(activityReporter));
            }

            return CreateInstanceAsync(
                folderToCacheRegionStampMap,
                HttpClientHelper.CreateHttpClient(ConnectionInfo.DefaultTimeout),
                ThirdPartyRegionStampMapUrl,
                activityReporter);
        }

        /// <summary>
        /// Gets the stamp endpoint.
        /// </summary>
        /// <param name="subscriptionId">The subscription identifier.</param>
        /// <param name="azureRegion">The azure region.</param>
        /// <returns>The stamp endpoint for the given account identified by <paramref name="subscriptionId"/>.</returns>
        public Uri GetStampEndpoint(string subscriptionId, string azureRegion)
        {
            var stampName = this.GetStampName(subscriptionId, azureRegion);
            return new Uri($"https://{stampName}{StampHostNameSuffix}");
        }

        /// <summary>
        /// Gets the stamp name.
        /// </summary>
        /// <param name="subscriptionId">The subscription identifier.</param>
        /// <param name="azureRegion">The azure region.</param>
        /// <returns>The stamp name for the given account identified by <paramref name="subscriptionId"/>.</returns>
        public string GetStampName(string subscriptionId, string azureRegion)
        {
            if (this.regionStampMap.ContainsKey(azureRegion))
            {
                return this.regionStampMap[azureRegion];
            }

            throw new MetricsClientException($"There is no MDM stamp for region [{azureRegion}]. Available regions are [{string.Join(",", this.regionStampMap.Keys)}].");
        }

        /// <summary>
        /// Creates the instance asynchronously.
        /// </summary>
        /// <param name="folderToCacheRegionStampMap">The folder to cache region stamp map.</param>
        /// <param name="httpClient">The HTTP client.</param>
        /// <param name="stampLocatorUrl">The stamp locator URL.</param>
        /// <param name="activityReporter">The activity reporter.</param>
        /// <returns>
        /// An instance of <see cref="StampLocator" />.
        /// </returns>
        internal static async Task<IStampLocator> CreateInstanceAsync(
            string folderToCacheRegionStampMap,
            HttpClient httpClient,
            Uri stampLocatorUrl,
            ActivityReporter activityReporter)
        {
            string fullFilePathForRegionStampMap = null;
            Dictionary<string, string> regionStampMap = null;
            if (!string.IsNullOrWhiteSpace(folderToCacheRegionStampMap) && Directory.Exists(folderToCacheRegionStampMap))
            {
                fullFilePathForRegionStampMap = Path.Combine(folderToCacheRegionStampMap, FileNameForRegionStampMap);
                if (File.Exists(fullFilePathForRegionStampMap))
                {
                    string fileContent = null;
                    activityReporter(StampLocatorActivity.StartToLoadRegionStampMapFromLocalFile, false, $"File name:{fullFilePathForRegionStampMap}.");
                    try
                    {
                        fileContent = File.ReadAllText(fullFilePathForRegionStampMap);
                        regionStampMap = CreateNewRegionStampMap(fileContent);
                        activityReporter(StampLocatorActivity.FinishedLoadingRegionStampMapFromLocalFile, false, $"fileContent:{fileContent}, regionStampMap:{JsonConvert.SerializeObject(regionStampMap)}.");
                    }
                    catch (Exception e)
                    {
                        var errorMessage =
                            $"Failed to create the region stamp map from local file. FilePath:{fullFilePathForRegionStampMap}, FileContent:{fileContent}, Exception:{e}.";
                        activityReporter(StampLocatorActivity.FailedToLoadRegionStampMapFromLocalFile, true, errorMessage);
                    }
                }
                else
                {
                    activityReporter(StampLocatorActivity.FailedToLoadRegionStampMapFromLocalFile, true, $"File {fullFilePathForRegionStampMap} doesn't exist.");
                }
            }
            else
            {
                activityReporter(StampLocatorActivity.FailedToLoadRegionStampMapFromLocalFile, true, $"Folder {folderToCacheRegionStampMap} doesn't exist.");
            }

            var instance = new StampLocator(fullFilePathForRegionStampMap, regionStampMap, httpClient, stampLocatorUrl, activityReporter);

            await instance.RefreshNoThrow().ConfigureAwait(false);

            if (instance.regionStampMap == null || instance.regionStampMap.Count == 0)
            {
                throw new MetricsClientException("The region stamp map failed to initialize or is empty.");
            }

            instance.timerToRefreshRegionStampMap = new Timer(async state => await instance.RefreshNoThrow().ConfigureAwait(false), null, instance.refreshInternal, instance.refreshInternal);

            return instance;
        }

        private static Dictionary<string, string> CreateNewRegionStampMap(string response)
        {
            var dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
            var newRegionStampMap = new Dictionary<string, string>(2 * dictionary.Count, StringComparer.OrdinalIgnoreCase);
            foreach (var kvp in dictionary)
            {
                newRegionStampMap.Add(kvp.Key, kvp.Value);
                newRegionStampMap.Add(kvp.Key.Replace(" ", string.Empty), kvp.Value);
            }

            return newRegionStampMap;
        }

        private async Task<bool> RefreshNoThrow()
        {
            this.activityReporter(StampLocatorActivity.StartToRefrehRegionStampMap, false, $"Url:{this.stampLocatorUrl}.");
            string response = null;
            try
            {
                var responseMessage = await HttpClientHelper.GetResponse(this.stampLocatorUrl, HttpMethod.Get, this.httpClient, "MA", "RefreshMap").ConfigureAwait(false);
                response = responseMessage.Item1;

                this.regionStampMap = CreateNewRegionStampMap(response);

                this.activityReporter(StampLocatorActivity.FinishedRefreshingRegionStampMap, false, $"regionStampMap:{JsonConvert.SerializeObject(this.regionStampMap)}.");
            }
            catch (Exception e)
            {
                string errorMessage = $"Failed to refresh the region stamp map. Response:{response}, Url:{this.stampLocatorUrl}, Exception:{e}.";
                this.activityReporter(StampLocatorActivity.FailedToRefrehRegionStampMap, true, errorMessage);
            }

            if (this.fullFilePathForRegionStampMap != null && response != null)
            {
                try
                {
                    this.activityReporter(StampLocatorActivity.StartToWriteRegionStampMapToLocalFile, false, $"File name:{this.fullFilePathForRegionStampMap}.");
                    File.WriteAllText(this.fullFilePathForRegionStampMap, response);
                    this.activityReporter(StampLocatorActivity.FinishedWritingRegionStampMapToLocalFile, false, string.Empty);
                }
                catch (Exception e)
                {
                    string errorMessage = $"Writing to {this.fullFilePathForRegionStampMap} failed with {e}.";
                    this.activityReporter(StampLocatorActivity.FailedToWriteRegionStampMapToLocalFile, true, errorMessage);
                }
            }

            return true;
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="CompositeExpression.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;

    /// <summary>
    /// Expression to be evaluated to create sampling types for the composite metric.
    /// </summary>
    public sealed class CompositeExpression : IExpression
    {
        private string name;
        private string expression;

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositeExpression"/> class.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="expression">The expression.</param>
        public CompositeExpression(string name, string expression)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                throw new ArgumentNullException(nameof(name));
            }

            if (string.IsNullOrWhiteSpace(expression))
            {
                throw new ArgumentNullException(nameof(expression));
            }

            this.name = name;
            this.expression = expression;
        }

        /// <summary>
        /// Gets or sets the name of the expression.
        /// </summary>
        public string Name
        {
            get
            {
                return this.name;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.name = value;
            }
        }

        /// <summary>
        /// Gets or sets the expression.
        /// </summary>
        public string Expression
        {
            get
            {
                return this.expression;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.expression = value;
            }
        }
    }
}
<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="IMonitorHealthStatus.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization.Monitor
{
    using System;

    /// <summary>
    /// The interface representing the monitor health status.
    /// </summary>
    public interface IMonitorHealthStatus
    {
        /// <summary>
        /// Gets a value indicating the healthy status of the last report.
        /// </summary>
        bool Healthy { get; }

        /// <summary>
        /// Gets the last time when monitor reported a status
        /// </summary>
        DateTimeOffset? TimeStamp { get; }

        /// <summary>
        /// Gets the message for the last monitor report
        /// </summary>
        string Message { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IPermission.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;

    /// <summary>
    /// This object represents one entity and their level of access to an MDM Account.
    /// </summary>
    [Obsolete]
    public interface IPermission
    {
        /// <summary>
        /// The identity to grant permission.
        /// </summary>
        string Identity { get; }

        /// <summary>
        /// The level of access to be granted to this identity.
        /// </summary>
        Role Role { get; set; }
    }
}
<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DiagnosticHeartbeatReader.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.MetricsExtension
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;

    using Logging;
    using Metrics.Etw;

    /// <summary>
    /// The class for consumption of ME diagnostic heart beats.
    /// </summary>
    public sealed unsafe class DiagnosticHeartbeatReader
    {
        /// <summary>
        /// Prefix added to ETW sessions that are using the configuration of a collector.
        /// </summary>
        private const string EtwSessionsPrefix = "DiagnosticHeartbeatReader-";

        /// <summary>
        /// Custom log id to be used in the log statements.
        /// </summary>
        private static readonly object LogId = Logger.CreateCustomLogId("DiagnosticHeartbeatReader");

        /// <summary>
        /// The provider unique identifier.
        /// </summary>
        private static readonly Guid ProviderGuid = new Guid("{2F23A2A9-0DE7-4CB4-A778-FBDF5C1E7372}");

        /// <summary>
        /// Gets or sets a value indicating whether [enable verbose logging].
        /// </summary>
        public bool EnableVerboseLogging { get; set; }

        /// <summary>
        /// Reads the diagnostic heartbeats.
        /// </summary>
        /// <param name="heartbeatAction">The heartbeat action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The task listening for ETW events.</returns>
        public Task ReadDiagnosticHeartbeatsAsync(
            Action<IDiagnosticHeartbeat> heartbeatAction,
            CancellationToken cancellationToken)
        {
            if (heartbeatAction == null)
            {
                throw new ArgumentNullException(nameof(heartbeatAction));
            }

            if (cancellationToken == null)
            {
                throw new ArgumentNullException(nameof(cancellationToken));
            }

            if (!this.EnableVerboseLogging)
            {
                Logger.SetMaxLogLevel(LoggerLevel.Error);
            }

            ActiveCollector activeCollector = null;
            Task task = Task.Factory.StartNew(
                () =>
                {
                    try
                    {
                        Task.Factory.StartNew(
                            () =>
                            SetupListener(
                                ProviderGuid,
                                heartbeatAction,
                                cancellationToken,
                                out activeCollector),
                            TaskCreationOptions.LongRunning);

                        cancellationToken.WaitHandle.WaitOne();
                    }
                    finally
                    {
                        StopEtwSession(activeCollector);
                    }
                },
                TaskCreationOptions.LongRunning);

            Console.CancelKeyPress += (sender, args) => StopEtwSession(activeCollector);

            return task;
        }

        /// <summary>
        /// Stops the etw session.
        /// </summary>
        /// <param name="activeCollector">The active collector.</param>
        private static void StopEtwSession(ActiveCollector activeCollector)
        {
            if (activeCollector != null)
            {
                ActiveCollector.StopCollector(activeCollector.Name);
            }
        }

        /// <summary>
        /// Setups the listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="action">The action.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <param name="activeCollector">The active collector.</param>
        private static void SetupListener(
            Guid providerGuid,
            Action<IDiagnosticHeartbeat> action,
            CancellationToken cancellationToken,
            out ActiveCollector activeCollector)
        {
            var providers = new Dictionary<Guid, ProviderConfiguration>
                                {
                                    {
                                        providerGuid,
                                        new ProviderConfiguration(providerGuid, EtwTraceLevel.Verbose, 0, 0)
                                    }
                                };

            var etwSessionConfig = new CollectorConfiguration(EtwSessionsPrefix + "-")
            {
                SessionType = SessionType.Realtime,
                Providers = providers
            };

            activeCollector = new ActiveCollector(etwSessionConfig.Name);
            activeCollector.StartCollector(etwSessionConfig);

            RawListener etwListener = null;
            try
            {
                etwListener = CreateRealTimeListener(providerGuid, etwSessionConfig.Name, action, 2, cancellationToken);

                // TODO: Better to check providers periodically and retry several times.
                if (!ActiveCollector.TryUpdateProviders(etwSessionConfig))
                {
                    Logger.Log(LoggerLevel.Error, LogId, "Main", "Failed to update ETW providers. Terminating.");
                    return;
                }

                try
                {
                    etwListener.Process();
                }
                finally
                {
                    Logger.Log(
                        cancellationToken.IsCancellationRequested ? LoggerLevel.Info : LoggerLevel.Error,
                        LogId,
                        "SetupEtwDataPipeline",
                        "ETW Thread terminated unexpectedly, typically indicates that the ETW session was stopped.");
                }
            }
            finally
            {
                etwListener?.Dispose();
            }
        }

        /// <summary>
        /// Creates the real time listener.
        /// </summary>
        /// <param name="providerGuid">The provider unique identifier.</param>
        /// <param name="etlSessionConfigName">Name of the etl session configuration.</param>
        /// <param name="action">The action.</param>
        /// <param name="eventIdFilter">Event Id filter to call the action</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>The ETW listener</returns>
        private static RawListener CreateRealTimeListener(
            Guid providerGuid,
            string etlSessionConfigName,
            Action<IDiagnosticHeartbeat> action,
            int eventIdFilter,
            CancellationToken cancellationToken)
        {
            return RawListener.CreateRealTimeListener(
                etlSessionConfigName,
                eventRecord =>
                {
                    if (eventRecord->EventHeader.ProviderId == providerGuid && eventRecord->EventHeader.Id == eventIdFilter)
                    {
                        action(DiagnosticHeartbeat.FromEtwEvent(eventRecord));
                    }
                },
                eventTraceLog =>
                {
                    Logger.Log(
                        LoggerLevel.Info,
                        LogId,
                        "CreateRealTimeListener",
                        "DiagnosticHeartbeat, cancelled = {0}",
                        cancellationToken.IsCancellationRequested);

                    return !cancellationToken.IsCancellationRequested;
                });
        }
    }
}<file_sep>﻿using k8s;
using k8s.Models;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace K8SClient
{
    public class K8sOperations
    {
        public static async Task<List<Extensionsv1beta1Ingress>> ListAllIngressAsync(IKubernetes client, string ns, CancellationToken cancellationToken = default)
        {
            var results = new List<Extensionsv1beta1Ingress>();
            string continueParameter = null;
            do
            {
                var resp = await client.ListNamespacedIngressAsync(ns, continueParameter: continueParameter, limit: 100, cancellationToken: cancellationToken);
                continueParameter = resp.Metadata.ContinueProperty;
                results.AddRange(resp.Items);
            } while (continueParameter != null);
            return results;
        }

        public static async Task<List<V1Service>> ListAllServiceAsync(IKubernetes client, string ns, CancellationToken cancellationToken = default)
        {
            var results = new List<V1Service>();
            string continueParameter = null;
            do
            {
                var resp = await client.ListNamespacedServiceAsync(ns, continueParameter: continueParameter, limit: 100, cancellationToken: cancellationToken);
                continueParameter = resp.Metadata.ContinueProperty;
                results.AddRange(resp.Items);
            } while (continueParameter != null);
            return results;
        }

        public static async Task<List<V1Deployment>> ListAllDeployments(IKubernetes client, string ns, CancellationToken cancellationToken = default)
        {
            var results = new List<V1Deployment>();
            string continueParameter = null;
            do
            {
                var resp = await client.ListNamespacedDeploymentAsync(ns, continueParameter: continueParameter, limit: 100, cancellationToken: cancellationToken);
                continueParameter = resp.Metadata.ContinueProperty;
                results.AddRange(resp.Items);
            } while (continueParameter != null);
            return results;
        }

        public static async Task<List<V1ReplicaSet>> ListReplicaSet(IKubernetes client, string ns, CancellationToken cancellationToken = default)
        {
            var results = new List<V1ReplicaSet>();
            string continueParameter = null;
            do
            {
                var resp = await client.ListNamespacedReplicaSetAsync(ns, continueParameter: continueParameter, limit: 100, cancellationToken: cancellationToken);
                continueParameter = resp.Metadata.ContinueProperty;
                results.AddRange(resp.Items);
            } while (continueParameter != null);
            return results;
        }

        public static async Task<List<V1Pod>> ListPodsAsync(IKubernetes client, string ns, CancellationToken cancellationToken = default)
        {
            var list = new List<V1Pod>();
            string continueParameter = null;
            do
            {
                var resp = await client.ListNamespacedPodAsync(ns, continueParameter: continueParameter, limit: 100, cancellationToken: cancellationToken);
                continueParameter = resp.Metadata.ContinueProperty;
                list.AddRange(resp.Items);
            } while (continueParameter != null);
            return list;
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IFilteringConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// Determines if the filtering is enabled for this pre-aggregate based on configured pre-aggregate filters.
    /// </summary>
    public interface IFilteringConfiguration
    {
        /// <summary>
        /// Determines if the filtering is enabled for this pre-aggregate based on configured pre-aggregate filters.
        /// </summary>
        bool Enabled { get; }
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IPercentileConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// Determine if the percentile sampling types are available for this preaggregate.
    /// </summary>
    public interface IPercentileConfiguration
    {
        /// <summary>
        /// Determines if the percentile sampling types are enabled or disabled.
        /// </summary>
        bool Enabled { get; }
    }
}<file_sep>//-------------------------------------------------------------------------------------------------
// <copyright file="IBlobSegment.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Metrics.Services.Common.BlobSegment
{
    /// <summary>
    /// Blob segment interface
    /// </summary>
    public interface IBlobSegment : IPoolTrackable
    {
        /// <summary>
        /// Gets the data that is stored in the segment.
        /// </summary>
        byte[] Data { get; }

        /// <summary>
        /// Gets or sets the reference to the next segment in the blob (null for the last segment of the blob).
        /// </summary>
        IBlobSegment Next { get; set; }

        /// <summary>
        /// Initializes an instance of BlobSegment class.
        /// </summary>
        /// <param name="nextSegment">The next segment in the linked list.</param>
        /// <param name="purpose">Purpose of the segment usage. Useful for debugging memory leaks.</param>
        void Initialize(
            IBlobSegment nextSegment,
            SegmentPurpose purpose);
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="CompositeMetricSource.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    using System;

    /// <summary>
    /// Identity and display name of the metric that will be used in the composite.
    /// </summary>
    public class CompositeMetricSource
    {
        private string displayName;
        private string monitoringAccount;
        private string metricNamespace;
        private string metric;

        /// <summary>
        /// Initializes a new instance of the <see cref="CompositeMetricSource"/> class.
        /// </summary>
        /// <param name="displayName">The display name.</param>
        /// <param name="monitoringAccount">The monitoring account.</param>
        /// <param name="metricNamespace">The metric namespace.</param>
        /// <param name="metric">The metric.</param>
        public CompositeMetricSource(string displayName, string monitoringAccount, string metricNamespace, string metric)
        {
            if (string.IsNullOrWhiteSpace(displayName))
            {
                throw new ArgumentNullException(nameof(displayName));
            }

            if (string.IsNullOrWhiteSpace(monitoringAccount))
            {
                throw new ArgumentNullException(nameof(monitoringAccount));
            }

            if (string.IsNullOrWhiteSpace(metricNamespace))
            {
                throw new ArgumentNullException(nameof(metricNamespace));
            }

            if (string.IsNullOrWhiteSpace(metric))
            {
                throw new ArgumentNullException(nameof(metric));
            }

            this.displayName = displayName;
            this.monitoringAccount = monitoringAccount;
            this.metricNamespace = metricNamespace;
            this.metric = metric;
        }

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        public string DisplayName
        {
            get
            {
                return this.displayName;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.displayName = value;
            }
        }

        /// <summary>
        /// Gets or sets the monitoring account.
        /// </summary>
        public string MonitoringAccount
        {
            get
            {
                return this.monitoringAccount;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.monitoringAccount = value;
            }
        }

        /// <summary>
        /// Gets or sets the metric namespace.
        /// </summary>
        public string MetricNamespace
        {
            get
            {
                return this.metricNamespace;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.metricNamespace = value;
            }
        }

        /// <summary>
        /// Gets or sets the metric.
        /// </summary>
        public string Metric
        {
            get
            {
                return this.metric;
            }

            set
            {
                if (string.IsNullOrWhiteSpace(value))
                {
                    throw new ArgumentNullException(nameof(value));
                }

                this.metric = value;
            }
        }
    }
}<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="MonitorHealthStatus.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System;

    using Microsoft.Online.Metrics.Serialization.Monitor;

    /// <summary>
    /// The class representing the monitor status.
    /// </summary>
    internal sealed class MonitorHealthStatus : IMonitorHealthStatus
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MonitorHealthStatus"/> class.
        /// </summary>
        /// <param name="healthy">if set to <c>true</c> [healthy].</param>
        /// <param name="timeStamp">The time stamp of the last monitor report.</param>
        /// <param name="message">The message for the last monitor report.</param>
        public MonitorHealthStatus(bool healthy, DateTimeOffset? timeStamp, string message)
        {
            this.Healthy = healthy;
            this.TimeStamp = timeStamp;
            this.Message = message;
        }

        /// <summary>
        /// Gets a value indicating the healthy status of the last report.
        /// </summary>
        public bool Healthy { get; private set; }

        /// <summary>
        /// Gets the last time when monitor reported a status
        /// </summary>
        public DateTimeOffset? TimeStamp { get; private set; }

        /// <summary>
        /// Gets the message for the last monitor report
        /// </summary>
        public string Message { get; private set; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CrcCheckFailedSerializationException.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// <author email="selavrin">
//     <NAME>
// </author>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;

    /// <summary>
    /// An exception used to report about situation when CRC check fails.
    /// </summary>
    [Serializable]
    public sealed class CrcCheckFailedSerializationException : MetricSerializationException
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CrcCheckFailedSerializationException"/> class.
        /// </summary>
        /// <param name="message">A message explaining the cause for exception situation.</param>
        public CrcCheckFailedSerializationException(string message)
            : base(message, null)
        {
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="Extensions.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client
{
    using System;
    using System.Collections.Generic;

    using Microsoft.Online.Metrics.Serialization.Configuration;

    /// <summary>
    /// The extension class.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Gets the index of the <paramref name="dimensionName"/> in dimension combination list.
        /// </summary>
        /// <param name="definitions">The time series definitions.</param>
        /// <param name="dimensionName">Name of the dimension.</param>
        /// <returns>The index of the <paramref name="dimensionName"/> in dimension combination list, or -1 if not found.</returns>
        public static int GetIndexInDimensionCombination(this IReadOnlyList<TimeSeriesDefinition<MetricIdentifier>> definitions, string dimensionName)
        {
            if (definitions == null || definitions.Count == 0)
            {
                throw new ArgumentException("definitions is null or empty.");
            }

            if (string.IsNullOrWhiteSpace(dimensionName))
            {
                throw new ArgumentException("dimensionName is null or empty.");
            }

            var definition = definitions[0];
            if (definition.DimensionCombination == null)
            {
                return -1;
            }

            for (int i = 0; i < definition.DimensionCombination.Count; ++i)
            {
                if (dimensionName.Equals(definition.DimensionCombination[i].Key, StringComparison.OrdinalIgnoreCase))
                {
                    return i;
                }
            }

            return -1;
        }
    }
}
<file_sep>﻿using AzSignalR.Monitor.JobRegistry;
using k8s;
using K8SClient;
using McMaster.Extensions.CommandLineUtils;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace MetricsMonitor
{
    [VersionOptionFromMember(MemberName = nameof(GetVersion))]
    [HelpOption("--help")]
    [Subcommand(
        typeof(ScanPodMetricsOption))]
    internal class CommandLineOptions : BaseOption
    {
        public string GetVersion()
            => typeof(CommandLineOptions).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;

        protected override Task OnExecuteAsync(CommandLineApplication app)
        {
            app.ShowHelp();
            return Task.CompletedTask;
        }
    }

    
    [Command(Name = "podmetrics", FullName = "podmetrics", Description = "Scan the metrics for all pods in a specific region")]
    internal class ScanPodMetricsOption : BaseOption
    {
        [Option("-k|--k8sConfig", Description = "Specify the k8s configuration")]
        public string K8sConfig { get; set; }

        [Option("-r|--region", Description = "Specify the region where you want to get metrics from")]
        public string Region { get; set; }

        [Option("-c|--certPath", Description = "Specify the mdm client certificate path. Default is mdm.pfx")]
        public string MdmClientCertPath { get; set; } = "mdm.pfx";

        [Option("-p|--certPasswd", Description = "Specify the certificate password")]
        public string MdmClientCertPasswd { get; set; }

        [Option("-m|--metricsName", Description = "Specify the metrics name: <PodConnectionCount>|<MessageCount>, default is PodConnectionCount")]
        public string MetricsName { get; set; } = MdmClient.PodConnectionCount;

        private async Task<string[]> GetFreeTierDeployments(IKubernetes client, CancellationToken cancellationToken = default)
        {
            var deployments = await K8sOperations.ListAllDeployments(client, "default");
            var freeDeployments = deployments.Where(d => d.Spec.Replicas == 1).Select(d => d.Metadata.Name).ToArray();
            return freeDeployments;
        }

        private bool isFreeTierPod(string[] freeTierList, string podName)
        {
            if (freeTierList != null && freeTierList.Length > 0)
            {
                foreach (var free in freeTierList)
                {
                    if (podName.StartsWith(free))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        protected override async Task OnExecuteAsync(CommandLineApplication app)
        {
            if (!ValidateParameters())
            {
                return;
            }
            var kubeClientCache = new KubernetesClientCache();
            var kubeClient = kubeClientCache.Get("CheckPods", File.ReadAllText(this.K8sConfig));
            var pods = await K8sOperations.ListPodsAsync(kubeClient, "default");
            var freeTierDeployArray = await GetFreeTierDeployments(kubeClient);
            var mdmClient = new MdmClient(MdmClientCertPath, MdmClientCertPasswd, Region);
            var podMetrics = await mdmClient.GetDimensionCountMetricsAsync(MetricsName, TimeSpan.FromHours(1), "InstanceId");
            if (podMetrics == null)
            {
                Console.WriteLine($"Fail to get metrics for {Region}");
                return;
            }
            bool foundFreeTier = false;
            Parallel.ForEach(pods, pod =>
            {
                bool isFreeTier = isFreeTierPod(freeTierDeployArray, pod.Metadata.Name);
                if (isFreeTier)
                {
                    foundFreeTier = true;
                }
                var hasMetrics = podMetrics.TryGetValue(pod.Metadata.Name, out double count);
                if (!hasMetrics)
                {
                    if (isFreeTier)
                    {
                        Console.WriteLine($"Free pod {pod.Metadata.Name} miss metrics");
                    }
                    else
                    {
                        Console.WriteLine($"{pod.Metadata.Name} miss metrics");
                    }
                }
            });
            if (!foundFreeTier)
            {
                Console.WriteLine("Does not see free tier");
            }
        }

        private bool ValidateParameters()
        {
            if (String.IsNullOrEmpty(this.K8sConfig))
            {
                Console.WriteLine("Missing k8s configuration, please specify it by -k");
                return false;
            }
            if (String.IsNullOrEmpty(this.Region))
            {
                Console.WriteLine("Missing region, please specify it by -r");
                return false;
            }
            if (String.IsNullOrEmpty(this.MdmClientCertPath))
            {
                Console.WriteLine("Missing Mdm client certification, please specify it by -c");
                return false;
            }
            if (String.IsNullOrEmpty(this.MdmClientCertPasswd))
            {
                Console.WriteLine("Missing Mdm client certification password, please specify it by -p");
                return false;
            }
            return true;
        }
    }

    [HelpOption("--help")]
    internal abstract class BaseOption
    {
        protected virtual Task OnExecuteAsync(CommandLineApplication app)
        {
            return Task.CompletedTask;
        }

        protected static void ReportError(Exception ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.Error.WriteLine($"Unexpected error: {ex}");
            Console.ResetColor();
        }
    }
}
<file_sep>﻿//-------------------------------------------------------------------------------------------------
// <copyright file="IPublicationConfiguration.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Configuration
{
    /// <summary>
    /// Determine which metric storage should be used for the data from this preaggregate.
    /// </summary>
    public interface IPublicationConfiguration
    {
        /// <summary>
        /// Gets or sets a value indicating whether cache server publication is disabled.
        /// </summary>
        bool CacheServerPublicationDisabled { get; }

        /// <summary>
        /// Determines if the feature is enabled or disabled.
        /// </summary>
        bool MetricStorePublicationEnabled { get; }

        /// <summary>
        /// Gets a value indicating whether the preaggregate should be published as an aggregated metrics store metric.
        /// </summary>
        bool AggregatedMetricsStorePublication { get; }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PropertyDefinition.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Query
{
    using System;
    using Metrics;
    using Newtonsoft.Json;

    /// <summary>
    /// Aggregation types which can be used to create properties based on time series data
    /// </summary>
    public enum PropertyAggregationType
    {
        Average,
        Sum,
        Min,
        Max
    }

    /// <summary>
    /// Defines the calculation that will occur to get a query service property.  The sampling type to act
    /// on, and the aggregation to perform on that sampling type.
    /// </summary>
    public sealed class PropertyDefinition
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyDefinition"/> class.
        /// </summary>
        /// <param name="propertyAggregationType">Type of the property aggregation.</param>
        /// <param name="samplingType">Name of the sampling type.</param>
        [JsonConstructor]
        public PropertyDefinition(PropertyAggregationType propertyAggregationType, SamplingType samplingType)
        {
            this.PropertyAggregationType = propertyAggregationType;
            this.SamplingType = samplingType;
            this.PropertyName = GetPropertyName(this.PropertyAggregationType, this.SamplingType.Name);
        }

        /// <summary>
        /// Gets the type of the property aggregation.
        /// </summary>
        public PropertyAggregationType PropertyAggregationType { get; }

        /// <summary>
        /// Gets the name of the sampling type.
        /// </summary>
        public SamplingType SamplingType { get; }

        /// <summary>
        /// Gets the name of the property.
        /// </summary>
        public string PropertyName { get; }

        /// <summary>
        /// Gets the name of the resulted property.
        /// </summary>
        /// <param name="propertyAggregationType">Property aggregation type.</param>
        /// <param name="samplingTypeName">Sampling type to which aggregation is applied.</param>
        /// <returns>Name of the property.</returns>
        public static string GetPropertyName(PropertyAggregationType propertyAggregationType, string samplingTypeName)
        {
            switch (propertyAggregationType)
            {
                case PropertyAggregationType.Average:
                    return $"TAVG({samplingTypeName})";
                case PropertyAggregationType.Sum:
                    return $"TSUM({samplingTypeName})";
                case PropertyAggregationType.Max:
                    return $"TMAX({samplingTypeName})";
                case PropertyAggregationType.Min:
                    return $"TMIN({samplingTypeName})";
                default:
                    throw new ArgumentException($"Unexpected propertyAggregationType: {propertyAggregationType}.", nameof(propertyAggregationType));
            }
        }
    }
}
<file_sep>﻿// -----------------------------------------------------------------------
// <copyright file="LocalRawMetric.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

namespace Microsoft.Cloud.Metrics.Client.Metrics
{
    using System;
    using System.Collections.Generic;

    using Etw;
    using Logging;

    /// <summary>
    /// A class representing data for one raw metric.
    /// </summary>
    internal sealed class LocalRawMetric : ILocalRawMetric
    {
        /// <summary>
        /// The platform metric etw operation code
        /// </summary>
        private const int PlatformMetricEtwOperationCode = 51;

        /// <summary>
        /// Custom log id to be used in the log statements.
        /// </summary>
        private static readonly object LogId = Logger.CreateCustomLogId("LocalRawMetric");

        /// <summary>
        /// Gets the Monitoring Account to which this metric is reported.
        /// </summary>
        public string MonitoringAccount { get; private set; }

        /// <summary>
        /// Gets the metric namespace.
        /// </summary>
        public string MetricNamespace { get; private set; }

        /// <summary>
        /// Gets the metric name.
        /// </summary>
        public string MetricName { get; private set; }

        /// <summary>
        /// Gets the metric dimensions.
        /// </summary>
        public IDictionary<string, string> Dimensions { get; private set; }

        /// <summary>
        /// Gets a value indicating whether the metric is a platform metric.
        /// In such case its value should be taken using property <see cref="MetricDoubleValue"/>.
        /// </summary>
        public bool IsPlatformMetric { get; private set; }

        /// <summary>
        /// Gets the metric time bucket.
        /// </summary>
        public DateTime MetricTimeUtc { get; private set; }

        /// <summary>
        /// Gets the sample value of metric emitted using Metric API.
        /// </summary>
        public ulong MetricLongValue { get; private set; }

        /// <summary>
        /// Gets the sample value of the Platform specific metric.
        /// </summary>
        public double MetricDoubleValue { get; private set; }

        /// <summary>
        /// Converts content of the ETW event to <see cref="LocalRawMetric"/>.
        /// </summary>
        /// <param name="etwMetricData">Object containing information about metric data sample.</param>
        /// <returns>A <see cref="LocalRawMetric"/> object representing metric sample data.</returns>
        /// <exception cref="ArgumentException">Throw when information contained in metricDataRecord is in incorrect format.</exception>
        internal static unsafe LocalRawMetric ConvertToMetricData(NativeMethods.EventRecord* etwMetricData)
        {
            try
            {
                // Read ETW event time and use as metric time
                var etwTimeUtc = DateTime.FromFileTimeUtc(etwMetricData->EventHeader.TimeStamp);

                IntPtr pointerInPayload = etwMetricData->UserData;

                // Get number of dimensions
                ushort dimensionsCount = *((ushort*)pointerInPayload);
                pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(ushort));

                // Shift 6 bytes as this space is reserved for alignment
                pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(ushort) + sizeof(uint));

                // If time was reported with metric, use it
                long timestamp = *((long*)pointerInPayload);
                pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(long));

                var metricTimeUtc = timestamp == 0 ? etwTimeUtc : DateTime.FromFileTimeUtc(timestamp);

                // Metric sample value which is either value or delta
                double doubleMetricSampleValue = 0;
                long metricSampleValue = 0;
                if (etwMetricData->EventHeader.Id == PlatformMetricEtwOperationCode)
                {
                    doubleMetricSampleValue = *((double*)pointerInPayload);
                }
                else
                {
                    metricSampleValue = *((long*)pointerInPayload);
                }

                pointerInPayload = EtwPayloadManipulationUtils.Shift(pointerInPayload, sizeof(long));

                // Read monitoring account, metric namespace and name
                var monitoringAccount = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);
                var metricNameSpace = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);
                var metricName = EtwPayloadManipulationUtils.ReadString(ref pointerInPayload);

                var dimensionNames = new List<string>();
                for (int i = 0; i < dimensionsCount; ++i)
                {
                    dimensionNames.Add(EtwPayloadManipulationUtils.ReadString(ref pointerInPayload));
                }

                var dimensionValues = new List<string>();
                for (int i = 0; i < dimensionsCount; ++i)
                {
                    dimensionValues.Add(EtwPayloadManipulationUtils.ReadString(ref pointerInPayload));
                }

                var dimensions = new Dictionary<string, string>();
                for (int i = 0; i < dimensionsCount; ++i)
                {
                    dimensions[dimensionNames[i]] = dimensionValues[i];
                }

                return new LocalRawMetric
                {
                    IsPlatformMetric =
                        etwMetricData->EventHeader.Id == PlatformMetricEtwOperationCode,
                    MetricTimeUtc = metricTimeUtc,
                    MetricLongValue = (ulong)metricSampleValue,
                    MetricDoubleValue = doubleMetricSampleValue,
                    MonitoringAccount = monitoringAccount,
                    MetricNamespace = metricNameSpace,
                    MetricName = metricName,
                    Dimensions = dimensions
                };
            }
            catch (Exception e)
            {
                Logger.Log(
                    LoggerLevel.Error,
                    LogId,
                    "ConvertToMetricData",
                    "Failed to read raw metric daat from the ETW event payload.",
                    e);

                throw;
            }
        }
    }
}<file_sep>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SamplingTypes.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace Microsoft.Online.Metrics.Serialization
{
    using System;

    /// <summary>
    /// Lists sampling types which metric may contain.
    /// </summary>
    [Flags]
    public enum SamplingTypes
    {
        None = 0x0,
        Min = 0x1,
        Max = 0x2,
        Sum = 0x4,
        Count = 0x10,
        Histogram = 0x20,
        SumCount = Sum | Count,
        SumCountMinMax = SumCount | Min | Max,
        HyperLogLogSketch = 0x40,
        DisableTrim = 0x80, // Not a sampling type per se but useful to send the information without changing serialization versions
        ClientSideLastSampleOnly = 0x100,
        DoubleValueType = 0x200, // Not a sampling type per se but useful to send the information without changing serialization versions
        DoubleValueStoredAsLongType = 0x400, // Not a sampling type per se but useful to send the information without changing serialization versions
        AggregatedTimeSeries = 0x800, // A flag indicating that samples belong to the aggregated time series (used in mStore aggregation pipeline)
        SendToCacheServer = 0x1000, // A flag indicating that samples aggregated on the FrontEnd should be sent to CacheServer
        SendToMStore = 0x2000, // A flag indicating that samples aggregated on the FrontEnd should be sent to mStore
        ComplexTypes = Histogram | HyperLogLogSketch,
        SumOfSquareDiffFromMean = 0x4000, // Used for calculating standard deviation, for more info look article https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_Online_algorithm
        TDigest = 0x8000,
    }
}
