diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d4769dae5..5d1c011152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ This changelog will be used to generate documentation on [release notes page](http://azure.microsoft.com/documentation/articles/app-insights-release-notes-dotnet/). +## Version 2.11.0-beta2 +- [Fix: Emit warning if user sets both Sampling IncludedTypes and ExcludedTypes. Excluded will take precedence.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1166) +- [Minor perf improvement by reading Actity.Tag only if required.](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1170) +- [Fix: Channels not handling AggregateException, not logging full HttpRequestException.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1173) +- [Metric Aggregator background thread safeguards added to never throw unhandled exception.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1179) +- [Updated version of System.Diagnostics.DiagnosticSource to 4.6.0-preview7.19362.9. Also remove marking SDK as CLS-Compliant](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1183) +- [Enhancement: Exceptions thrown by the TelemetryConfiguration will now specify the exact name of the property that could not be parsed from a config file.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1194) +- [Fix: ServerTelemetryChannel constructor exception when network info API throws.](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1184) +- [Make BaseSDK use W3C Trace Context based correlation by default. Set TelemetryConfiguration.EnableW3CCorrelation=false to disable this.](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1193) +- [Removed TelemetryConfiguration.EnableW3CCorrelation. Users should do Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical; Activity.ForceDefaultIdFormat = true; to disable W3C Format](https://github.com/microsoft/ApplicationInsights-dotnet/issues/1198) +- [Enable sampling based on upstream sampling decision for adaptive sampling](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1200) +- [Fix: StartOperation ignores user-provided custom Ids in scope of Activity](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1205) +- [Set tracestate if available on requests and dependencies](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1207) + ## Version 2.11.0-beta1 - [Performance fixes: Support Head Sampling; Remove NewGuid(); Sampling Flags; etc... ](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1158) - [Deprecate TelemetryConfiguration.Active on .NET Core in favor of dependency injection pattern](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1152) diff --git a/GlobalStaticVersion.props b/GlobalStaticVersion.props index 47dbfa8cb2..37e892e00a 100644 --- a/GlobalStaticVersion.props +++ b/GlobalStaticVersion.props @@ -9,7 +9,7 @@ 11 0 - beta1 + beta2 + True True + snupkg diff --git a/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt b/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt index 76adc0770c..59a7caf3a8 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt @@ -29,19 +29,31 @@ Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ItemTypeFlag.get Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.set -> void \ No newline at end of file +Microsoft.ApplicationInsights.Extensibility.Implementation.TaskTimer.TaskTimer() -> void +Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrapper.HttpWebResponseWrapper() -> void +Microsoft.ApplicationInsights.Extensibility.SequencePropertyInitializer.SequencePropertyInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.OperationTelemetry.OperationTelemetry() -> void +Microsoft.ApplicationInsights.Extensibility.OperationCorrelationTelemetryInitializer.OperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId.DictionaryApplicationIdProvider.DictionaryApplicationIdProvider() -> void +Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.None = 0 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledIn = 1 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledOut = 2 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.set -> void diff --git a/PublicAPI/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt b/PublicAPI/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt index 76adc0770c..59a7caf3a8 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt @@ -29,19 +29,31 @@ Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ItemTypeFlag.get Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.set -> void \ No newline at end of file +Microsoft.ApplicationInsights.Extensibility.Implementation.TaskTimer.TaskTimer() -> void +Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrapper.HttpWebResponseWrapper() -> void +Microsoft.ApplicationInsights.Extensibility.SequencePropertyInitializer.SequencePropertyInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.OperationTelemetry.OperationTelemetry() -> void +Microsoft.ApplicationInsights.Extensibility.OperationCorrelationTelemetryInitializer.OperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId.DictionaryApplicationIdProvider.DictionaryApplicationIdProvider() -> void +Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.None = 0 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledIn = 1 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledOut = 2 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.set -> void diff --git a/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard1.3/PublicAPI.Unshipped.txt b/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard1.3/PublicAPI.Unshipped.txt index 76adc0770c..59a7caf3a8 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard1.3/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard1.3/PublicAPI.Unshipped.txt @@ -29,19 +29,31 @@ Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ItemTypeFlag.get Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.set -> void \ No newline at end of file +Microsoft.ApplicationInsights.Extensibility.Implementation.TaskTimer.TaskTimer() -> void +Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrapper.HttpWebResponseWrapper() -> void +Microsoft.ApplicationInsights.Extensibility.SequencePropertyInitializer.SequencePropertyInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.OperationTelemetry.OperationTelemetry() -> void +Microsoft.ApplicationInsights.Extensibility.OperationCorrelationTelemetryInitializer.OperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId.DictionaryApplicationIdProvider.DictionaryApplicationIdProvider() -> void +Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.None = 0 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledIn = 1 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledOut = 2 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.set -> void diff --git a/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard2.0/PublicAPI.Unshipped.txt b/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard2.0/PublicAPI.Unshipped.txt index b6defbb047..ea35d1e78b 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard2.0/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Microsoft.ApplicationInsights.Extensibility.Implementation.Experimental.ExperimentalFeaturesExtension Microsoft.ApplicationInsights.Extensibility.W3C.W3CUtilities static Microsoft.ApplicationInsights.Extensibility.W3C.W3CUtilities.GenerateTraceId() -> string Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer @@ -15,6 +16,8 @@ static Microsoft.ApplicationInsights.Extensibility.W3C.W3CActivityExtensions.Set static Microsoft.ApplicationInsights.Extensibility.W3C.W3CActivityExtensions.UpdateContextOnActivity(this System.Diagnostics.Activity activity) -> System.Diagnostics.Activity static Microsoft.ApplicationInsights.Extensibility.W3C.W3CActivityExtensions.UpdateTelemetry(this System.Diagnostics.Activity activity, Microsoft.ApplicationInsights.Channel.ITelemetry telemetry, bool forceUpdate) -> void Microsoft.ApplicationInsights.TelemetryClient.InitializeInstrumentationKey(Microsoft.ApplicationInsights.Channel.ITelemetry telemetry) -> void +Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.ExperimentalFeatures.get -> System.Collections.Generic.IList +static Microsoft.ApplicationInsights.Extensibility.Implementation.Experimental.ExperimentalFeaturesExtension.EvaluateExperimentalFeature(this Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration telemetryConfiguration, string featureName) -> bool Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryConfigurationExtensions static Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryConfigurationExtensions.GetLastObservedSamplingPercentage(this Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration configuration, Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes samplingItemType) -> double static Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryConfigurationExtensions.SetLastObservedSamplingPercentage(this Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration configuration, Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes samplingItemType, double value) -> void @@ -43,22 +46,31 @@ Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ItemTypeFlag.get Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ItemTypeFlag.get -> Microsoft.ApplicationInsights.DataContracts.SamplingTelemetryItemTypes -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.get -> bool -Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.EventTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.IsSampledOutAtHead.set -> void -Microsoft.ApplicationInsights.Extensibility.Implementation.Experimental.ExperimentalFeaturesExtension -Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.ExperimentalFeatures.get -> System.Collections.Generic.IList -static Microsoft.ApplicationInsights.Extensibility.Implementation.Experimental.ExperimentalFeaturesExtension.EvaluateExperimentalFeature(this Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration telemetryConfiguration, string featureName) -> bool \ No newline at end of file +Microsoft.ApplicationInsights.Extensibility.Implementation.TaskTimer.TaskTimer() -> void +Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrapper.HttpWebResponseWrapper() -> void +Microsoft.ApplicationInsights.Extensibility.SequencePropertyInitializer.SequencePropertyInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.OperationTelemetry.OperationTelemetry() -> void +Microsoft.ApplicationInsights.Extensibility.OperationCorrelationTelemetryInitializer.OperationCorrelationTelemetryInitializer() -> void +Microsoft.ApplicationInsights.Extensibility.Implementation.ApplicationId.DictionaryApplicationIdProvider.DictionaryApplicationIdProvider() -> void +Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.None = 0 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledIn = 1 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.SamplingDecision.SampledOut = 2 -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.get -> Microsoft.ApplicationInsights.DataContracts.SamplingDecision +Microsoft.ApplicationInsights.DataContracts.TraceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.RequestTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.DependencyTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.EventTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewPerformanceTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry.ProactiveSamplingDecision.set -> void +Microsoft.ApplicationInsights.DataContracts.ISupportAdvancedSampling.ProactiveSamplingDecision.set -> void diff --git a/Signing.targets b/Signing.targets index d90c50ae59..802be10ff3 100644 --- a/Signing.targets +++ b/Signing.targets @@ -8,7 +8,7 @@ - Microsoft + Microsoft400 MsSharedLib72 @@ -23,7 +23,7 @@ - MicrosoftSHA1 + Microsoft400 diff --git a/Test/Microsoft.ApplicationInsights.Test/Net45/Microsoft.ApplicationInsights.Net45.Tests.csproj b/Test/Microsoft.ApplicationInsights.Test/Net45/Microsoft.ApplicationInsights.Net45.Tests.csproj index dfbd4c1d5f..7bc49faed3 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Net45/Microsoft.ApplicationInsights.Net45.Tests.csproj +++ b/Test/Microsoft.ApplicationInsights.Test/Net45/Microsoft.ApplicationInsights.Net45.Tests.csproj @@ -31,7 +31,7 @@ - + diff --git a/Test/Microsoft.ApplicationInsights.Test/Net46/Microsoft.ApplicationInsights.Net46.Tests.csproj b/Test/Microsoft.ApplicationInsights.Test/Net46/Microsoft.ApplicationInsights.Net46.Tests.csproj index 39e70a5aad..c631e6021f 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Net46/Microsoft.ApplicationInsights.Net46.Tests.csproj +++ b/Test/Microsoft.ApplicationInsights.Test/Net46/Microsoft.ApplicationInsights.Net46.Tests.csproj @@ -31,7 +31,7 @@ - + diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Common/ExceptionExtensionsTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Common/ExceptionExtensionsTests.cs new file mode 100644 index 0000000000..910080313f --- /dev/null +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Common/ExceptionExtensionsTests.cs @@ -0,0 +1,30 @@ +namespace Microsoft.ApplicationInsights.Common +{ + using System; + using Microsoft.ApplicationInsights.Common.Extensions; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ExceptionExtensionsTests + { + [TestMethod] + public void VerifyCanFlattenMultipleExceptions() + { + var ex1 = new Exception("a"); + var ex2 = new Exception("b", ex1); + var ex3 = new Exception("c", ex2); + + var test = ex3.FlattenMessages(); + Assert.AreEqual("c | b | a", test); + } + + [TestMethod] + public void VerifyCanFlattenSingleException() + { + var ex1 = new Exception("a"); + + var test = ex1.FlattenMessages(); + Assert.AreEqual("a", test); + } + } +} diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/DependencyTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/DependencyTelemetryTest.cs index 7a08a0810c..299d1d6fe7 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/DependencyTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/DependencyTelemetryTest.cs @@ -30,7 +30,7 @@ public void VerifyExpectedDefaultValue() Assert.IsNotNull(defaultDependencyTelemetry.ResultCode); Assert.IsNotNull(defaultDependencyTelemetry.Type); Assert.IsNotNull(defaultDependencyTelemetry.Id); - Assert.IsFalse(defaultDependencyTelemetry.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, defaultDependencyTelemetry.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.RemoteDependency, defaultDependencyTelemetry.ItemTypeFlag); Assert.IsTrue(defaultDependencyTelemetry.Id.Length >= 1); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/EventTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/EventTelemetryTest.cs index 86b02f9bac..f5f58ede38 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/EventTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/EventTelemetryTest.cs @@ -17,7 +17,7 @@ public class EventTelemetryTest public void VerifyExpectedDefaultValue() { var eventTelemetry = new EventTelemetry(); - Assert.IsFalse(eventTelemetry.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, eventTelemetry.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Event, eventTelemetry.ItemTypeFlag); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/ExceptionTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/ExceptionTelemetryTest.cs index 64d42c0fdd..4193337f48 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/ExceptionTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/ExceptionTelemetryTest.cs @@ -23,7 +23,7 @@ public class ExceptionTelemetryTest public void VerifyExpectedDefaultValue() { var exceptionTelemetry = new ExceptionTelemetry(); - Assert.IsFalse(exceptionTelemetry.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, exceptionTelemetry.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Exception, exceptionTelemetry.ItemTypeFlag); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewPerformanceTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewPerformanceTelemetryTest.cs index 406cbe25c7..405555c309 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewPerformanceTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewPerformanceTelemetryTest.cs @@ -21,7 +21,7 @@ public class PageViewPerformanceTelemetryTest public void VerifyExpectedDefaultValue() { var pageViewPerformanceTelemetry = new PageViewPerformanceTelemetry(); - Assert.IsFalse(pageViewPerformanceTelemetry.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, pageViewPerformanceTelemetry.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.PageViewPerformance, pageViewPerformanceTelemetry.ItemTypeFlag); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewTelemetryTest.cs index f2d156442d..4951553ff0 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/PageViewTelemetryTest.cs @@ -21,7 +21,7 @@ public class PageViewTelemetryTest public void VerifyExpectedDefaultValue() { var pageViewTelemetry = new PageViewTelemetry(); - Assert.IsFalse(pageViewTelemetry.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, pageViewTelemetry.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.PageView, pageViewTelemetry.ItemTypeFlag); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/RequestTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/RequestTelemetryTest.cs index 59229ab677..af15c7e30b 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/RequestTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/RequestTelemetryTest.cs @@ -37,7 +37,7 @@ public void ParameterlessConstructorInitializesRequiredFields() Assert.IsFalse(request.Duration == null); Assert.IsTrue(request.Success == null); Assert.IsTrue(request.Data.success); - Assert.IsFalse(request.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, request.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Request, request.ItemTypeFlag); } @@ -51,7 +51,7 @@ public void ParameterizedConstructorInitializesNewInstanceWithGivenNameTimestamp Assert.AreEqual(TimeSpan.FromSeconds(42), request.Duration); Assert.AreEqual(true, request.Success); Assert.AreEqual(start, request.Timestamp); - Assert.IsFalse(request.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, request.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Request, request.ItemTypeFlag); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/TraceTelemetryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/TraceTelemetryTest.cs index f52abe49d5..b0a1fb9e08 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/TraceTelemetryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/DataContracts/TraceTelemetryTest.cs @@ -35,7 +35,7 @@ public void ConstructorInitializesDefaultTraceTelemetryInstance() Assert.IsNotNull(item.Properties); AssertEx.IsEmpty(item.Message); Assert.IsNull(item.SeverityLevel); - Assert.IsFalse(item.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, item.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Message, item.ItemTypeFlag); } @@ -47,7 +47,7 @@ public void ConstructorInitializesTraceTelemetryInstanceWithGivenMessage() Assert.IsNotNull(item.Properties); Assert.AreEqual("TestMessage", item.Message); Assert.IsNull(item.SeverityLevel); - Assert.IsFalse(item.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, item.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Message, item.ItemTypeFlag); } @@ -59,7 +59,7 @@ public void ConstructorInitializesTraceTelemetryInstanceWithGivenMessageAndSever Assert.IsNotNull(trace.Properties); Assert.AreEqual("TestMessage", trace.Message); Assert.AreEqual(SeverityLevel.Critical, trace.SeverityLevel); - Assert.IsFalse(trace.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.None, trace.ProactiveSamplingDecision); Assert.AreEqual(SamplingTelemetryItemTypes.Message, trace.ItemTypeFlag); } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/OperationHolderTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/OperationHolderTests.cs index 21707eb19e..751d91fa59 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/OperationHolderTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/OperationHolderTests.cs @@ -1,7 +1,9 @@ namespace Microsoft.ApplicationInsights.Extensibility.Implementation { using System; + using System.Diagnostics; using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.TestFramework; using Microsoft.VisualStudio.TestTools.UnitTesting; /// @@ -10,6 +12,12 @@ [TestClass] public class OperationHolderTests { + [TestInitialize] + public void Initialize() + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + /// /// Tests the scenario if OperationItem throws ArgumentNullException with null telemetry client. /// @@ -38,5 +46,51 @@ public void CreatingOperationItemDoesNotThrowOnPassingValidArguments() { var operationItem = new OperationHolder(new TelemetryClient(TelemetryConfiguration.CreateDefault()), new DependencyTelemetry()); } + + [TestMethod] + public void CreatingOperationHolderWithDetachedOriginalActivityRestoresIt() + { + var client = new TelemetryClient(TelemetryConfiguration.CreateDefault()); + + var originalActivity = new Activity("original").Start(); + var operation = new OperationHolder(client, new DependencyTelemetry(), originalActivity); + + var newActivity = new Activity("new").SetParentId("detached-parent").Start(); + operation.Telemetry.Id = $"|{newActivity.TraceId.ToHexString()}.{newActivity.SpanId.ToHexString()}."; + + operation.Dispose(); + Assert.AreEqual(Activity.Current, originalActivity); + } + + [TestMethod] + public void CreatingOperationHolderWithNullOriginalActivityDoesNotRestoreIt() + { + var client = new TelemetryClient(TelemetryConfiguration.CreateDefault()); + + var originalActivity = new Activity("original").Start(); + var operation = new OperationHolder(client, new DependencyTelemetry(), null); + + var newActivity = new Activity("new").SetParentId("detached-parent").Start(); + operation.Telemetry.Id = $"|{newActivity.TraceId.ToHexString()}.{newActivity.SpanId.ToHexString()}."; + + operation.Dispose(); + Assert.IsNull(Activity.Current); + } + + [TestMethod] + public void CreatingOperationHolderWithParentActivityRestoresIt() + { + var client = new TelemetryClient(TelemetryConfiguration.CreateDefault()); + + var originalActivity = new Activity("original").Start(); + var operation = new OperationHolder(client, new DependencyTelemetry(), originalActivity); + + // child of original + var newActivity = new Activity("new").Start(); + operation.Telemetry.Id = $"|{newActivity.TraceId.ToHexString()}.{newActivity.SpanId.ToHexString()}."; + operation.Dispose(); + Assert.AreEqual(Activity.Current, originalActivity); + } + } } diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/Platform/PlatformImplementationTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/Platform/PlatformImplementationTest.cs index 169eafa80f..b410667f6d 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/Platform/PlatformImplementationTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/Implementation/Platform/PlatformImplementationTest.cs @@ -55,7 +55,8 @@ public void FailureToReadEnvironmentVariablesDoesNotThrowExceptions() { permission.PermitOnly(); PlatformImplementation platform = new PlatformImplementation(); - Assert.IsNull(platform.GetEnvironmentVariable("PATH")); + Assert.IsFalse(platform.TryGetEnvironmentVariable("PATH", out string value)); + Assert.IsNull(value); permission = null; } finally diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs index 5a8eb1cb4a..c3ec3e16c1 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs @@ -1,46 +1,192 @@ namespace Microsoft.ApplicationInsights.Extensibility { using System.Diagnostics; + using System.Linq; using Implementation; using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.Extensibility.W3C; + using Microsoft.ApplicationInsights.TestFramework; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class OperationCorrelationTelemetryInitializerTests { + private static TelemetryConfiguration tc; + + [ClassInitialize] + public static void Init(TestContext ctx) + { + // Constructor on TelemetryConfiguration forces Activity.IDFormat to be W3C + // OperationCorrelationTelemetryInitializer has no responsibility to set the Activity Format. + // It expects Activity to use W3CFormat, but falls back to use Hierrarchial Id. + tc = new TelemetryConfiguration(); + } + + [TestInitialize] + public void TestInit() + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + + [TestCleanup] + public void Cleanup() + { + while (Activity.Current != null) + { + Activity.Current.Stop(); + } + } + [TestMethod] public void InitializerDoesNotFailOnNullCurrentActivity() { + // Arrange + // Does not start Activity. + var telemetry = new DependencyTelemetry(); + + // Act (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + // Validate + // Initialize is a no-op, and no exceptions thrown. + Assert.IsNull(telemetry.Context.Operation.Id); Assert.IsNull(telemetry.Context.Operation.ParentId); + Assert.IsFalse(telemetry.Properties.Any()); } [TestMethod] - public void TelemetryContextIsUpdatedWithOperationIdForDependencyTelemetry() + public void InitializePopulatesOperationContextFromActivity() { - Activity parent = new Activity("parent").SetParentId("ParentOperationId").Start(); - + // Arrange + Activity activity = new Activity("somename"); + activity.Start(); var telemetry = new DependencyTelemetry(); - (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + var originalTelemetryId = telemetry.Id; - Assert.AreEqual("ParentOperationId", telemetry.Context.Operation.Id); - Assert.AreEqual(parent.Id, telemetry.Context.Operation.ParentId); - parent.Stop(); + // Act + var initializer = new OperationCorrelationTelemetryInitializer(); + initializer.Initialize(telemetry); + + // Validate + Assert.AreEqual(activity.TraceId.ToHexString(), telemetry.Context.Operation.Id, "OperationCorrelationTelemetryInitializer is expected to populate OperationID from Activity"); + Assert.AreEqual(W3CUtilities.FormatTelemetryId(activity.TraceId.ToHexString(), activity.SpanId.ToHexString()), + telemetry.Context.Operation.ParentId, + "OperationCorrelationTelemetryInitializer is expected to populate Operation ParentID as |traceID.SpanId. from Activity"); + Assert.AreEqual(originalTelemetryId, telemetry.Id, "OperationCorrelationTelemetryInitializer is not expected to modify Telemetry ID"); + activity.Stop(); } [TestMethod] - public void InitializeDoesNotUpdateOperationIdIfItExists() + public void InitializePopulatesOperationContextFromActivityWhenW3CIsDisabled() { - Activity parent = new Activity("parent").SetParentId("ParentOperationId").Start(); + // Arrange + ActivityFormatHelper.DisableW3CFormatInActivity(); + try + { + Activity parent = new Activity("parent"); + + // Setting parentid like this forces Activity to use Hierarchical ID Format + parent.SetParentId("parent"); + parent.Start(); + + var telemetry = new DependencyTelemetry(); + var initializer = new OperationCorrelationTelemetryInitializer(); + + // Act + initializer.Initialize(telemetry); + + // Validate + Assert.AreEqual("parent", telemetry.Context.Operation.Id); + Assert.AreEqual(parent.Id, telemetry.Context.Operation.ParentId); + parent.Stop(); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + [TestMethod] + public void InitializeDoesNotOverrideOperationIdIfItExists() + { + // Arrange var telemetry = new DependencyTelemetry(); telemetry.Context.Operation.ParentId = "OldParentOperationId"; - (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + telemetry.Context.Operation.Id = "OldOperationId"; + var initializer = new OperationCorrelationTelemetryInitializer(); + Activity parent = new Activity("parent"); + parent.Start(); + + // Act + initializer.Initialize(telemetry); + + // Validate Assert.AreEqual("OldParentOperationId", telemetry.Context.Operation.ParentId); + Assert.AreEqual("OldOperationId", telemetry.Context.Operation.Id); + parent.Stop(); + } + + [TestMethod] + public void InitializeDoesNotOverrideEmptyParentIdIfOperationIdExists() + { + // Arrange + var telemetry = new DependencyTelemetry(); + telemetry.Context.Operation.Id = "OldOperationId"; + // Does not set parentid and hence it'll be empty + var initializer = new OperationCorrelationTelemetryInitializer(); + Activity parent = new Activity("parent"); + parent.Start(); + + initializer.Initialize(telemetry); + + Assert.IsNull(telemetry.Context.Operation.ParentId, "Operation.ParentID should not be overwritten when Operation.ID is already present"); + Assert.AreEqual("OldOperationId", telemetry.Context.Operation.Id, "Operation should not be overwritten"); parent.Stop(); } + [TestMethod] + public void InitializeDoesntOverrideContextIfOperationIdSet() + { + var currentActivity = new Activity("test"); + currentActivity.AddTag("OperationName", "operation"); + currentActivity.AddBaggage("k1", "v1"); + currentActivity.AddBaggage("k2", "v2"); + currentActivity.Start(); + var telemetry = new RequestTelemetry(); + telemetry.Context.Operation.Id = "operationId"; + telemetry.Context.Operation.ParentId = null; + telemetry.Context.Operation.Name = "operation"; + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + Assert.AreEqual("operationId", telemetry.Context.Operation.Id); + Assert.IsNull(telemetry.Context.Operation.ParentId); + Assert.AreEqual("operation", telemetry.Context.Operation.Name); + Assert.AreEqual(0, telemetry.Properties.Count); + currentActivity.Stop(); + } + + [TestMethod] + public void InitializeOverridesContextIfOperationIdIsNotSet() + { + var currentActivity = new Activity("test"); + currentActivity.AddTag("OperationName", "operation"); + currentActivity.AddBaggage("k1", "v1"); + currentActivity.Start(); + var telemetry = new TraceTelemetry(); + + telemetry.Context.Operation.ParentId = "parentId"; + telemetry.Context.Operation.Name = "operation"; + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + Assert.AreEqual(currentActivity.TraceId.ToHexString(), telemetry.Context.Operation.Id); + Assert.AreEqual("parentId", telemetry.Context.Operation.ParentId); + Assert.AreEqual("operation", telemetry.Context.Operation.Name); + Assert.AreEqual(1, telemetry.Properties.Count); + Assert.AreEqual("v1", telemetry.Properties["k1"]); + currentActivity.Stop(); + } + [TestMethod] public void TelemetryContextIsUpdatedWithOperationNameForDependencyTelemetry() { @@ -55,7 +201,7 @@ public void TelemetryContextIsUpdatedWithOperationNameForDependencyTelemetry() } [TestMethod] - public void InitilaizeWithActivityWithoutOperationName() + public void InitializeWithActivityWithoutOperationName() { var currentActivity = new Activity("test"); currentActivity.Start(); @@ -67,6 +213,20 @@ public void InitilaizeWithActivityWithoutOperationName() currentActivity.Stop(); } + [TestMethod] + public void InitializeWithActivityWithOperationName() + { + var currentActivity = new Activity("test"); + currentActivity.AddTag("OperationName", "OperationName"); + currentActivity.Start(); + var telemetry = new RequestTelemetry(); + + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + Assert.AreEqual("OperationName", telemetry.Context.Operation.Name); + currentActivity.Stop(); + } + [TestMethod] public void InitializeDoesNotUpdateOperationNameIfItExists() { @@ -78,50 +238,47 @@ public void InitializeDoesNotUpdateOperationNameIfItExists() telemetry.Context.Operation.Name = "OldOperationName"; (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); - Assert.AreEqual(telemetry.Context.Operation.Name, "OldOperationName"); + Assert.AreEqual("OldOperationName",telemetry.Context.Operation.Name); parent.Stop(); } [TestMethod] - public void InitilaizeWithActivitySetsOperationContext() + public void InitializeSetsBaggage() { var currentActivity = new Activity("test"); - currentActivity.SetParentId("parent"); currentActivity.AddTag("OperationName", "operation"); currentActivity.AddBaggage("k1", "v1"); currentActivity.AddBaggage("k2", "v2"); + currentActivity.AddBaggage("existingkey", "exitingvalue"); currentActivity.Start(); var telemetry = new RequestTelemetry(); + telemetry.Properties.Add("existingkey", "exitingvalue"); (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); - Assert.AreEqual("parent", telemetry.Context.Operation.Id); - Assert.AreEqual(currentActivity.Id, telemetry.Context.Operation.ParentId); - Assert.IsTrue(telemetry.Context.Operation.ParentId.StartsWith("|parent.")); + Assert.AreEqual(currentActivity.TraceId.ToHexString(), telemetry.Context.Operation.Id); + Assert.AreEqual(W3CUtilities.FormatTelemetryId(currentActivity.TraceId.ToHexString(), currentActivity.SpanId.ToHexString()), telemetry.Context.Operation.ParentId); Assert.AreEqual("operation", telemetry.Context.Operation.Name); -#pragma warning disable CS0618 // Type or member is obsolete - Assert.AreEqual(2, telemetry.Context.Properties.Count); - Assert.AreEqual("v1", telemetry.Context.Properties["k1"]); - Assert.AreEqual("v2", telemetry.Context.Properties["k2"]); -#pragma warning restore CS0618 // Type or member is obsolete + Assert.AreEqual(3, telemetry.Properties.Count); + Assert.AreEqual("v1", telemetry.Properties["k1"]); + Assert.AreEqual("v2", telemetry.Properties["k2"]); + Assert.AreEqual("exitingvalue", telemetry.Properties["existingkey"], "OperationCorrelationTelemetryInitializer should not override existing telemetry property bag"); currentActivity.Stop(); } [TestMethod] - public void InitilaizeWithActivityWinsOverCallContext() + public void InitializeWithActivityWinsOverCallContext() { CallContextHelpers.SaveOperationContext(new OperationContextForCallContext { RootOperationId = "callContextRoot" }); var currentActivity = new Activity("test"); - currentActivity.SetParentId("activityRoot"); currentActivity.AddTag("OperationName", "operation"); currentActivity.AddBaggage("k1", "v1"); currentActivity.Start(); var telemetry = new RequestTelemetry(); (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); - Assert.AreEqual("activityRoot", telemetry.Context.Operation.Id); - Assert.AreEqual(currentActivity.Id, telemetry.Context.Operation.ParentId); - Assert.IsTrue(telemetry.Context.Operation.ParentId.StartsWith("|activityRoot.")); + Assert.AreEqual(currentActivity.TraceId.ToHexString(), telemetry.Context.Operation.Id); + Assert.AreEqual(W3CUtilities.FormatTelemetryId(currentActivity.TraceId.ToHexString(), currentActivity.SpanId.ToHexString()), telemetry.Context.Operation.ParentId); Assert.AreEqual("operation", telemetry.Context.Operation.Name); Assert.AreEqual(1, telemetry.Properties.Count); @@ -130,47 +287,131 @@ public void InitilaizeWithActivityWinsOverCallContext() } [TestMethod] - public void InitilaizeWithActivityDoesntOverrideContextIfRootIsSet() + public void InitializeWithActivityRecorded() { var currentActivity = new Activity("test"); - currentActivity.SetParentId("activityRoot"); - currentActivity.AddTag("OperationName", "test"); - currentActivity.AddBaggage("k1", "v1"); + currentActivity.ActivityTraceFlags |= ActivityTraceFlags.Recorded; currentActivity.Start(); - var telemetry = new TraceTelemetry(); + var request = new RequestTelemetry(); - telemetry.Context.Operation.Id = "rootId"; - telemetry.Context.Operation.ParentId = null; - telemetry.Context.Operation.Name = "operation"; - (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + (new OperationCorrelationTelemetryInitializer()).Initialize(request); - Assert.AreEqual("rootId", telemetry.Context.Operation.Id); - Assert.IsNull(telemetry.Context.Operation.ParentId); - Assert.AreEqual("operation", telemetry.Context.Operation.Name); - Assert.AreEqual(0, telemetry.Properties.Count); + Assert.AreEqual(SamplingDecision.SampledIn, request.ProactiveSamplingDecision); currentActivity.Stop(); } [TestMethod] - public void InitilaizeWithActivityOverridesContextIfRootIsNotSet() + public void InitializeWithActivityNotRecorded() { var currentActivity = new Activity("test"); - currentActivity.SetParentId("activityRoot"); - currentActivity.AddTag("OperationName", "test"); - currentActivity.AddBaggage("k1", "v1"); + currentActivity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; currentActivity.Start(); - var telemetry = new TraceTelemetry(); + var telemetry = new RequestTelemetry(); - telemetry.Context.Operation.ParentId = "parentId"; - telemetry.Context.Operation.Name = "operation"; (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); - Assert.AreEqual("activityRoot", telemetry.Context.Operation.Id); - Assert.AreEqual("parentId", telemetry.Context.Operation.ParentId); - Assert.AreEqual("operation", telemetry.Context.Operation.Name); - Assert.AreEqual(1, telemetry.Properties.Count); - Assert.AreEqual("v1", telemetry.Properties["k1"]); + Assert.AreEqual(SamplingDecision.None, telemetry.ProactiveSamplingDecision); + currentActivity.Stop(); + } + + [TestMethod] + public void InitializeWithActivityRecordedDoesNotOverrideSampledInIfSet() + { + var currentActivity = new Activity("test"); + currentActivity.ActivityTraceFlags |= ActivityTraceFlags.Recorded; + currentActivity.Start(); + var request = new RequestTelemetry + { + ProactiveSamplingDecision = SamplingDecision.SampledOut + }; + (new OperationCorrelationTelemetryInitializer()).Initialize(request); + + Assert.AreEqual(SamplingDecision.SampledOut, request.ProactiveSamplingDecision); currentActivity.Stop(); } + + [TestMethod] + public void InitializeWithActivityNotRecordedDoesNotOverrideSampledInIfSet() + { + var currentActivity = new Activity("test"); + currentActivity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + currentActivity.Start(); + var request = new RequestTelemetry + { + ProactiveSamplingDecision = SamplingDecision.SampledIn + }; + + + (new OperationCorrelationTelemetryInitializer()).Initialize(request); + + Assert.AreEqual(SamplingDecision.SampledIn, request.ProactiveSamplingDecision); + currentActivity.Stop(); + } + + [TestMethod] + public void InitializeOnActivityWithTracestate() + { + Activity parent = new Activity("parent") + { + TraceStateString = "some=state" + }; + parent.Start(); + + var telemetry = new DependencyTelemetry(); + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + Assert.IsTrue(telemetry.Properties.ContainsKey("tracestate")); + Assert.AreEqual("some=state", telemetry.Properties["tracestate"]); + } + + [TestMethod] + public void InitializeOnActivityWithTracestateW3COff() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + + Activity parent = new Activity("parent") + { + TraceStateString = "some=state" + }; + parent.Start(); + + var telemetry = new DependencyTelemetry(); + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + Assert.IsFalse(telemetry.Properties.ContainsKey("tracestate")); + } + + [TestMethod] + public void InitializeOnActivityWithTracestateWhenPropertyAlreadyExists() + { + Activity parent = new Activity("parent") + { + TraceStateString = "some=state" + }; + parent.Start(); + + var telemetry = new DependencyTelemetry(); + telemetry.Properties.Add("tracestate", "123"); + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + Assert.IsTrue(telemetry.Properties.ContainsKey("tracestate")); + Assert.AreEqual("123", telemetry.Properties["tracestate"]); + } + + + [TestMethod] + public void InitializeOnActivityWithTracestateNotOperationTelemetry() + { + Activity parent = new Activity("parent") + { + TraceStateString = "some=state" + }; + parent.Start(); + + var telemetry = new TraceTelemetry(); + + // does not throw + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + } } } \ No newline at end of file diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationFactoryTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationFactoryTest.cs index c56fb2aa53..d2b2753b8d 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationFactoryTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationFactoryTest.cs @@ -275,6 +275,7 @@ public void LoadInstanceSetsInstancePropertiesOfTimeSpanTypeFromChildElementValu } [TestMethod] + [ExpectedExceptionWithMessage(typeof(ArgumentException), "Failed to parse configuration value. Property: 'TimeSpanProperty' Reason: String was not recognized as a valid TimeSpan.")] public void LoadInstanceSetsInstancePropertiesOfTimeSpanTypeFromChildElementValuesOfDefinitionWithInvalidFormatThrowsException() { var definition = new XElement( @@ -282,7 +283,7 @@ public void LoadInstanceSetsInstancePropertiesOfTimeSpanTypeFromChildElementValu new XAttribute("Type", typeof(StubClassWithProperties).AssemblyQualifiedName), new XElement("TimeSpanProperty", "TestValue")); - AssertEx.Throws(() => TestableTelemetryConfigurationFactory.LoadInstance(definition, typeof(StubClassWithProperties), null, null)); + TestableTelemetryConfigurationFactory.LoadInstance(definition, typeof(StubClassWithProperties), null, null); } [TestMethod] @@ -915,6 +916,35 @@ public void LoadPropertiesGivesPrecedenceToValuesFromElementsBecauseTheyAppearBe Assert.AreEqual(42, instance.Int32Property); } + [TestMethod] + [ExpectedExceptionWithMessage(typeof(ArgumentException), "Failed to parse configuration value. Property: 'IntegerProperty' Reason: Input string was not in a correct format.")] + public void LoadPropertiesThrowsExceptionWithPropertyName() + { + // parsing this integer will throw "System.FormatException: Input string was not in a correct format." + // This is not useful without also specifying the errant property name. + + XElement definition = XDocument.Parse(Configuration( + @" + 123a + ")).Root; + + var instance = new TelemetryConfiguration(); + + TestableTelemetryConfigurationFactory.LoadProperties(definition, instance, null); + } + + [TestMethod] + [ExpectedExceptionWithMessage(typeof(ArgumentException), "Failed to parse configuration value. Property: 'IntegerProperty' Reason: Input string was not in a correct format.")] + public void LoadProperties_TelemetryClientThrowsException() + { + string testConfig = Configuration( + @" + 123a + "); + + new TelemetryClient(TelemetryConfiguration.CreateFromConfiguration(testConfig)); + } + [TestMethod] public void LoadPropertiesIgnoresNamespaceDeclarationWhenLoadingFromAttributes() { diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationTest.cs index 1197df9dca..628f2c7dff 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/TelemetryConfigurationTest.cs @@ -9,10 +9,32 @@ using Microsoft.ApplicationInsights.Extensibility.Implementation; using Microsoft.ApplicationInsights.TestFramework; using Microsoft.VisualStudio.TestTools.UnitTesting; - + using System.Diagnostics; + [TestClass] public class TelemetryConfigurationTest { + #region W3C + [TestMethod] + public void TelemetryConfigurationStaticConstructorSetsW3CToTrueIfNotEnforced() + { + try + { + // Accessing TelemetryConfiguration trigger static constructor + var tc = new TelemetryConfiguration(); + + Assert.IsTrue(Activity.ForceDefaultIdFormat); + Assert.AreEqual(ActivityIdFormat.W3C, Activity.DefaultIdFormat); + } + finally + { + Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical; + Activity.ForceDefaultIdFormat = false; + } + } + + #endregion + [TestMethod] public void TelemetryConfigurationIsPublicToAllowUsersManipulateConfigurationProgrammatically() { diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/MetricsExamples.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/MetricsExamples.cs index 74a33b98cf..818fd8bce2 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/MetricsExamples.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/MetricsExamples.cs @@ -1,4 +1,4 @@ -#pragma warning disable CA1716, 612, 618 // Namespace naming, obsolete TelemetryConfigration.Active +using Microsoft.ApplicationInsights.Extensibility; namespace User.Namespace.Example01 { @@ -21,78 +21,84 @@ public static void Exec() // Recall how you send custom telemetry with Application Insights in other cases, e.g. Events. // The following will result in an EventTelemetry object to be sent to the cloud right away. - TelemetryClient client = new TelemetryClient(); - client.TrackEvent("SomethingInterestingHappened"); + using (var config = TelemetryConfiguration.CreateDefault()) + { + TelemetryClient client = new TelemetryClient(config); + client.TrackEvent("SomethingInterestingHappened"); - // Metrics work very similar. However, the value is not sent right away. - // It is aggregated with other values for the same metric, and the resulting summary (aka "aggregate" is sent automatically every minute. - // To mark this difference, we use a pattern that is similar, but different from the established TrackXxx(..) pattern that sends telemetry right away: - client.GetMetric("CowsSold").TrackValue(42); + // Metrics work very similar. However, the value is not sent right away. + // It is aggregated with other values for the same metric, and the resulting summary (aka "aggregate" is sent automatically every minute. + // To mark this difference, we use a pattern that is similar, but different from the established TrackXxx(..) pattern that sends telemetry right away: + client.GetMetric("CowsSold").TrackValue(42); - // *** MEASUREMENTS AND ACCUMULATORS *** + // *** MEASUREMENTS AND ACCUMULATORS *** - // We support different kinds of aggregation types. For now, we include two: Measurements and Accumulators. - // Measurements aggregate tracked values and reduce them to {Count, Sum, Min, Max, StdDev} of all values tracked during each minute. - // They are particularly useful if you are measuring something like the number of items sold, the completion time of an operation, or similar. + // We support different kinds of aggregation types. For now, we include two: Measurements and Accumulators. + // Measurements aggregate tracked values and reduce them to {Count, Sum, Min, Max, StdDev} of all values tracked during each minute. + // They are particularly useful if you are measuring something like the number of items sold, the completion time of an operation, or similar. - // Accumulators are also sent to the cloud each minute. - // But rather than aggregating values across a time period, they aggregate values across their entire life time (or until you reset them). - // They are particularly useful when you are counting the number of items in a data structure. + // Accumulators are also sent to the cloud each minute. + // But rather than aggregating values across a time period, they aggregate values across their entire life time (or until you reset them). + // They are particularly useful when you are counting the number of items in a data structure. - // By default, metrics are aggregated as Measurements. Here is how you can define a metric to be aggregated as an Accumulator instead: + // By default, metrics are aggregated as Measurements. Here is how you can define a metric to be aggregated as an Accumulator instead: - // Using the Microsoft.ApplicationInsights.Metrics.Extensions package: - // Metric itemsInDatastructure = client.GetMetric("ItemsInDatastructure", MetricConfigurations.Common.Accumulator()); + // Using the Microsoft.ApplicationInsights.Metrics.Extensions package: + // Metric itemsInDatastructure = client.GetMetric("ItemsInDatastructure", MetricConfigurations.Common.Accumulator()); - // Using a private implementation: - Metric itemsInDatastructure = client.GetMetric( + // Using a private implementation: + Metric itemsInDatastructure = client.GetMetric( "ItemsInDatastructure", new Microsoft.ApplicationInsights.Metrics.MetricConfiguration( - 1000, - 100, - new Microsoft.ApplicationInsights.Metrics.TestUtility.MetricSeriesConfigurationForTestingAccumulatorBehavior())); + 1000, + 100, + new Microsoft.ApplicationInsights.Metrics.TestUtility. + MetricSeriesConfigurationForTestingAccumulatorBehavior())); - int itemsAdded = AddItemsToDataStructure(); - itemsInDatastructure.TrackValue(itemsAdded); + int itemsAdded = AddItemsToDataStructure(); + itemsInDatastructure.TrackValue(itemsAdded); - int itemsRemoved = AddItemsToDataStructure(); - itemsInDatastructure.TrackValue(-itemsRemoved); + int itemsRemoved = AddItemsToDataStructure(); + itemsInDatastructure.TrackValue(-itemsRemoved); - // Here is how you can reset an accumulator: - ResetDataStructure(); - itemsInDatastructure.GetAllSeries()[0].Value.ResetAggregation(); + // Here is how you can reset an accumulator: + ResetDataStructure(); + itemsInDatastructure.GetAllSeries()[0].Value.ResetAggregation(); - // *** MULTI-DIMENSIONAL METRICS *** + // *** MULTI-DIMENSIONAL METRICS *** - // The above example shows a zero-dimensional metric. - // Metrics can also be multi-dimensional. - // In the initial version we are supporting up to 2 dimensions, and we will add support for more in the future as needed. - // Here is an example for a one-dimensional metric: + // The above example shows a zero-dimensional metric. + // Metrics can also be multi-dimensional. + // In the initial version we are supporting up to 2 dimensions, and we will add support for more in the future as needed. + // Here is an example for a one-dimensional metric: - Metric animalsSold = client.GetMetric("AnimalsSold", "Species"); + Metric animalsSold = client.GetMetric("AnimalsSold", "Species"); - animalsSold.TrackValue(42, "Pigs"); - animalsSold.TrackValue(24, "Horses"); + animalsSold.TrackValue(42, "Pigs"); + animalsSold.TrackValue(24, "Horses"); - // The values for Pigs and Horses will be aggregated separately from each other and will result in two distinct aggregates. - // You can control the maximum number of number data series per metric (and thus your resource usage and cost). - // The default limits are no more than 1000 total data series per metric, and no more than 100 different values per dimension. - // We discuss elsewhere how to change them. - // We use a common .Net pattern: TryXxx(..) to make sure that the limits are observed. - // If the limits are already reached, Metric.TrackValue(..) will return False and the value will not be tracked. Otherwise it will return True. - // This is particularly useful if the data for a metric originates from user input, e.g. a file: + // The values for Pigs and Horses will be aggregated separately from each other and will result in two distinct aggregates. + // You can control the maximum number of number data series per metric (and thus your resource usage and cost). + // The default limits are no more than 1000 total data series per metric, and no more than 100 different values per dimension. + // We discuss elsewhere how to change them. + // We use a common .Net pattern: TryXxx(..) to make sure that the limits are observed. + // If the limits are already reached, Metric.TrackValue(..) will return False and the value will not be tracked. Otherwise it will return True. + // This is particularly useful if the data for a metric originates from user input, e.g. a file: - Tuple countAndSpecies = ReadSpeciesFromUserInput(); - int count = countAndSpecies.Item1; - string species = countAndSpecies.Item2; + Tuple countAndSpecies = ReadSpeciesFromUserInput(); + int count = countAndSpecies.Item1; + string species = countAndSpecies.Item2; - if (!animalsSold.TrackValue(count, species)) - { - client.TrackTrace($"Data series or dimension cap was reached for metric {animalsSold.Identifier.MetricId}.", TraceSeveretyLevel.Error); - } + if (!animalsSold.TrackValue(count, species)) + { + client.TrackTrace( + $"Data series or dimension cap was reached for metric {animalsSold.Identifier.MetricId}.", + TraceSeveretyLevel.Error); + } - // You can inspect a metric object to reason about its current state. For example: - int currentNumberOfSpecies = animalsSold.GetDimensionValues(1).Count; + // You can inspect a metric object to reason about its current state. For example: + int currentNumberOfSpecies = animalsSold.GetDimensionValues(1).Count; + } } private static void ResetDataStructure() @@ -142,108 +148,112 @@ public static void Exec() // *** ACCESSING METRIC DATA SERIES *** // Recall that metrics can be multidimensional. For example, assume that we want to track the number of books sold by Genre and by Language. + using (var config = TelemetryConfiguration.CreateDefault()) + { + TelemetryClient client = new TelemetryClient(config); + Metric booksSold = client.GetMetric("BooksSold", "Genre", "Language"); + booksSold.TrackValue(10, "Science Fiction", "English"); + booksSold.TrackValue(15, "Historic Novels", "English"); + booksSold.TrackValue(20, "Epic Tragedy", "Russian"); - TelemetryClient client = new TelemetryClient(); - Metric booksSold = client.GetMetric("BooksSold", "Genre", "Language"); - booksSold.TrackValue(10, "Science Fiction", "English"); - booksSold.TrackValue(15, "Historic Novels", "English"); - booksSold.TrackValue(20, "Epic Tragedy", "Russian"); - - // Recall from the previous example that each of the above TrackValue(..) statements will create a - // new data series and use it to track the specified value. - // If you use the same dimension values as before, then instead of creating a new series, the system will look up and use an existing series: + // Recall from the previous example that each of the above TrackValue(..) statements will create a + // new data series and use it to track the specified value. + // If you use the same dimension values as before, then instead of creating a new series, the system will look up and use an existing series: - booksSold.TrackValue(8, "Science Fiction", "English"); // Now we have 18 Science Fiction books in English + booksSold.TrackValue(8, "Science Fiction", + "English"); // Now we have 18 Science Fiction books in English - // If you use certain data series frequently you can avoid this lookup by keeping a reference to it: + // If you use certain data series frequently you can avoid this lookup by keeping a reference to it: - MetricSeries epicTragedyInRussianSold; - booksSold.TryGetDataSeries(out epicTragedyInRussianSold, "Epic Tragedy", "Russian"); - epicTragedyInRussianSold.TrackValue(6); // Now we have 26 Epic Tragedies in Russian - epicTragedyInRussianSold.TrackValue(5); // Now we have 31 Epic Tragedies in Russian + MetricSeries epicTragedyInRussianSold; + booksSold.TryGetDataSeries(out epicTragedyInRussianSold, "Epic Tragedy", "Russian"); + epicTragedyInRussianSold.TrackValue(6); // Now we have 26 Epic Tragedies in Russian + epicTragedyInRussianSold.TrackValue(5); // Now we have 31 Epic Tragedies in Russian - // Notice the "Try" in TryGetDataSeries(..). Recall the previous example where we explained the TrackValue(..) pattern. - // The same reasoning applies here. + // Notice the "Try" in TryGetDataSeries(..). Recall the previous example where we explained the TrackValue(..) pattern. + // The same reasoning applies here. - // So Metric is a container of one or more data series. - // The actual data belongs a specific MetricSeries object and the Metric object is a grouping of one or more series. + // So Metric is a container of one or more data series. + // The actual data belongs a specific MetricSeries object and the Metric object is a grouping of one or more series. - // A zero-dimensional metric has exactly one metric data series: - Metric cowsSold = client.GetMetric("CowsSold"); - Assert.AreEqual(0, cowsSold.Identifier.DimensionsCount); + // A zero-dimensional metric has exactly one metric data series: + Metric cowsSold = client.GetMetric("CowsSold"); + Assert.AreEqual(0, cowsSold.Identifier.DimensionsCount); - MetricSeries cowsSoldValues; - cowsSold.TryGetDataSeries(out cowsSoldValues); - cowsSoldValues.TrackValue(25); + MetricSeries cowsSoldValues; + cowsSold.TryGetDataSeries(out cowsSoldValues); + cowsSoldValues.TrackValue(25); - // For zero-dimensional metrics you can also get the series in a single line: - MetricSeries cowsSoldValues2 = cowsSold.GetAllSeries()[0].Value; + // For zero-dimensional metrics you can also get the series in a single line: + MetricSeries cowsSoldValues2 = cowsSold.GetAllSeries()[0].Value; - cowsSoldValues2.TrackValue(18); // Now we have 43 cows. - Assert.AreSame(cowsSoldValues, cowsSoldValues2, "The two series references point to the same object"); + cowsSoldValues2.TrackValue(18); // Now we have 43 cows. + Assert.AreSame(cowsSoldValues, cowsSoldValues2, "The two series references point to the same object"); - // Note, however, that you cannot play this trick with multi-dimensional series, because GetAllSeries() does - // not provide any guarantees about the ordering of the series it returns. + // Note, however, that you cannot play this trick with multi-dimensional series, because GetAllSeries() does + // not provide any guarantees about the ordering of the series it returns. - // Multi-dimensional metrics can have more than one data series: - MetricSeries unspecifiedBooksSold, cookbookInGermanSold; - booksSold.TryGetDataSeries(out unspecifiedBooksSold); - booksSold.TryGetDataSeries(out cookbookInGermanSold, "Cookbook", "German"); + // Multi-dimensional metrics can have more than one data series: + MetricSeries unspecifiedBooksSold, cookbookInGermanSold; + booksSold.TryGetDataSeries(out unspecifiedBooksSold); + booksSold.TryGetDataSeries(out cookbookInGermanSold, "Cookbook", "German"); - // You can get the "special" zero-dimensional series from every metric, regardless of now many dimensions it has. - // But if you specify any dimension values at all, you must specify the correct number, otherwise an exception is thrown. + // You can get the "special" zero-dimensional series from every metric, regardless of now many dimensions it has. + // But if you specify any dimension values at all, you must specify the correct number, otherwise an exception is thrown. - try - { - MetricSeries epicTragediesSold; - booksSold.TryGetDataSeries(out epicTragediesSold, "Epic Tragedy"); - } - catch (ArgumentException) - { - client.TrackTrace( - $"This error will always happen because '{nameof(booksSold)}' has 2 dimensions, but we only specified one.", - TraceSeveretyLevel.Error); - } + try + { + MetricSeries epicTragediesSold; + booksSold.TryGetDataSeries(out epicTragediesSold, "Epic Tragedy"); + } + catch (ArgumentException) + { + client.TrackTrace( + $"This error will always happen because '{nameof(booksSold)}' has 2 dimensions, but we only specified one.", + TraceSeveretyLevel.Error); + } - // The main purpose of keeping a reference to a metric data series is to use it directly for tracking data. - // It can improve the performance of your application, especially if you are tracking values to this series very frequently, - // as it avoids the lookups necessary to first get the metric and then the series within the metric. + // The main purpose of keeping a reference to a metric data series is to use it directly for tracking data. + // It can improve the performance of your application, especially if you are tracking values to this series very frequently, + // as it avoids the lookups necessary to first get the metric and then the series within the metric. - // *** SPECIAL DIMENSION NAMES *** + // *** SPECIAL DIMENSION NAMES *** - // Note that metrics do not usually respect the TelemetryContext of the TelemetryClient used to access the metric. - // There is a detailed discussion of the reasons and workarounds in a latter example. For now, just a clarification: + // Note that metrics do not usually respect the TelemetryContext of the TelemetryClient used to access the metric. + // There is a detailed discussion of the reasons and workarounds in a latter example. For now, just a clarification: + TelemetryClient specialClient = new TelemetryClient(config); + specialClient.Context.Operation.Name = "Special Operation"; + Metric specialOpsRequestSizeStats = specialClient.GetMetric("Special Operation Request Size"); + int requestSize = GetCurrentRequestSize(); + specialOpsRequestSizeStats.TrackValue(requestSize); - TelemetryClient specialClient = new TelemetryClient(); - specialClient.Context.Operation.Name = "Special Operation"; - Metric specialOpsRequestSizeStats = specialClient.GetMetric("Special Operation Request Size"); - int requestSize = GetCurrentRequestSize(); - specialOpsRequestSizeStats.TrackValue(requestSize); + // Metric aggregates sent by the above specialOpsRequestSizeStats-metric will NOT have their Context.Operation.Name set to "Special Operation". - // Metric aggregates sent by the above specialOpsRequestSizeStats-metric will NOT have their Context.Operation.Name set to "Special Operation". + // However, you can use special dimension names in order to specify TelemetryContext values. For example - // However, you can use special dimension names in order to specify TelemetryContext values. For example - - MetricSeries specialOpsRequestSize; - client.GetMetric("Request Size", "TelemetryContext.Operation.Name").TryGetDataSeries(out specialOpsRequestSize, "Special Operation"); - specialOpsRequestSize.TrackValue(120000); + MetricSeries specialOpsRequestSize; + client.GetMetric("Request Size", "TelemetryContext.Operation.Name") + .TryGetDataSeries(out specialOpsRequestSize, "Special Operation"); + specialOpsRequestSize.TrackValue(120000); - // When the metric aggregate is sent to the Application Insights cloud endpoint, its 'Context.Operation.Name' data field - // will be set to "Special Operation". - // Note: the values of this special dimension will be copied into the TelemetryContext and not be used as a 'normal' dimension. - // If you want to also keep an operation name dimension for normal metric exploration, you need to create a separate dimension - // for that purpose: + // When the metric aggregate is sent to the Application Insights cloud endpoint, its 'Context.Operation.Name' data field + // will be set to "Special Operation". + // Note: the values of this special dimension will be copied into the TelemetryContext and not be used as a 'normal' dimension. + // If you want to also keep an operation name dimension for normal metric exploration, you need to create a separate dimension + // for that purpose: - MetricSeries someOtherOpsRequestSize; - client.GetMetric("Request Size", MetricDimensionNames.TelemetryContext.Operation.Name, "Operation Name") - .TryGetDataSeries(out someOtherOpsRequestSize, "Some Other Operation", "Some Other Operation"); - someOtherOpsRequestSize.TrackValue(64000); + MetricSeries someOtherOpsRequestSize; + client.GetMetric("Request Size", MetricDimensionNames.TelemetryContext.Operation.Name, + "Operation Name") + .TryGetDataSeries(out someOtherOpsRequestSize, "Some Other Operation", "Some Other Operation"); + someOtherOpsRequestSize.TrackValue(64000); - // In this case, the aggregates of the someOtherOpsRequestSize-series will have a dimension "Operation Name" with the - // value "Some Other Operation", and, in addition, their Context.Operation.Name will be set to "Some Other Operation". + // In this case, the aggregates of the someOtherOpsRequestSize-series will have a dimension "Operation Name" with the + // value "Some Other Operation", and, in addition, their Context.Operation.Name will be set to "Some Other Operation". - // The static class MetricDimensionNames contains a list of constants for all special dimension names. + // The static class MetricDimensionNames contains a list of constants for all special dimension names. + } } private static int GetCurrentRequestSize() @@ -272,77 +282,82 @@ public class Sample02a /// public static void Exec() { - TelemetryClient client = new TelemetryClient(); + using (var config = TelemetryConfiguration.CreateDefault()) + { + TelemetryClient client = new TelemetryClient(config); - MetricSeries epicTragedyInRussianSold; + MetricSeries epicTragedyInRussianSold; - Metric booksSold = client.GetMetric("BooksSold", "Genre", "Language"); - booksSold.TryGetDataSeries(out epicTragedyInRussianSold, "Epic Tragedy", "Russian"); + Metric booksSold = client.GetMetric("BooksSold", "Genre", "Language"); + booksSold.TryGetDataSeries(out epicTragedyInRussianSold, "Epic Tragedy", "Russian"); - // *** WORKING WITH THE EMITTED METRIC DATA *** + // *** WORKING WITH THE EMITTED METRIC DATA *** - // In addition, there are additional operations that you can perform on a series. - // Most common of them are designed to support interactive consumption of tracked data. - // For example, you can reset the values aggregated so far during the current aggregation period to the initial state: + // In addition, there are additional operations that you can perform on a series. + // Most common of them are designed to support interactive consumption of tracked data. + // For example, you can reset the values aggregated so far during the current aggregation period to the initial state: - epicTragedyInRussianSold.TrackValue(42); // Now we have 42 Epic Tragedies in Russian - epicTragedyInRussianSold.ResetAggregation(); // Now we have 0 Epic Tragedies in Russian + epicTragedyInRussianSold.TrackValue(42); // Now we have 42 Epic Tragedies in Russian + epicTragedyInRussianSold.ResetAggregation(); // Now we have 0 Epic Tragedies in Russian - // For Measurements, resetting will not make a lot of sense in most cases. - // However, for Accumulators this may be necessary once in a while, for example when you cleared a data structure for - // which you were counting the contained items. + // For Measurements, resetting will not make a lot of sense in most cases. + // However, for Accumulators this may be necessary once in a while, for example when you cleared a data structure for + // which you were counting the contained items. - // Another powerful example for interacting with aggregated metric data is the ability to inspect the aggregation. - // This means that your application is not just sending metric telemetry for a later inspection, but is able to use its - // own metrics to drive its behavior. - // For example, the following code determines the currently most popular book and displays the information: + // Another powerful example for interacting with aggregated metric data is the ability to inspect the aggregation. + // This means that your application is not just sending metric telemetry for a later inspection, but is able to use its + // own metrics to drive its behavior. + // For example, the following code determines the currently most popular book and displays the information: - MetricAggregate mostPopularBookKind = null; - foreach (KeyValuePair seriesKvp in booksSold.GetAllSeries()) - { - MetricSeries currentBookInfo = seriesKvp.Value; - MetricAggregate currentBookKind = currentBookInfo.GetCurrentAggregateUnsafe(); - - if (currentBookKind == null) + MetricAggregate mostPopularBookKind = null; + foreach (KeyValuePair seriesKvp in booksSold.GetAllSeries()) { - continue; - } + MetricSeries currentBookInfo = seriesKvp.Value; + MetricAggregate currentBookKind = currentBookInfo.GetCurrentAggregateUnsafe(); - if (mostPopularBookKind == null) - { - mostPopularBookKind = currentBookKind; - } - else - { - double maxSum = mostPopularBookKind.GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, 0); - double currentSum = currentBookKind.GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, 0); + if (currentBookKind == null) + { + continue; + } - if (maxSum > currentSum) + if (mostPopularBookKind == null) { mostPopularBookKind = currentBookKind; } + else + { + double maxSum = mostPopularBookKind.GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, 0); + double currentSum = currentBookKind.GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, 0); + + if (maxSum > currentSum) + { + mostPopularBookKind = currentBookKind; + } + } } - } - if (mostPopularBookKind != null) - { - DisplayMostPopularBook(mostPopularBookKind); - } + if (mostPopularBookKind != null) + { + DisplayMostPopularBook(mostPopularBookKind); + } - // Notice the "...Unsafe" suffix in the MetricSeries.GetCurrentAggregateUnsafe() method. - // We added it to underline the need for two important considerations when using this method: - // a) It may return proper objects and nulls in a poorly predictable way: - // For performance reasons, we only create internal aggregators if there is any data to aggregate. - // Consider a situation where you tracked some values for a Measurement metric. So GetCurrentAggregateUnsafe() - // returns a valid object. At any time, the aggregation period (1 minute) could complete. The aggregate - // will be "snapped" and sent to the cloud. Now there is no more aggregate until you track more values during - // the ongoing aggregation period. - // b) Aggregator implementations may choose to optimize their multithreaded performance in a way such that the aggregates - // do not always reflect the latest state. Data will be synchronized correctly before being sent to the cloud at the end - // of the aggregation period, but it may be lagging behind a few milliseconds at other times or it may be inconsistent. - // E.g., following a TrackValue(..) invocation the Count statistic of an aggregate may already be updated, but its Sum - // statistic may not yet be updated. These errors are small and not statistically significant. However you should use - // unsafe aggregates for what they are - statistical summaries, rather than exact counts. + // Notice the "...Unsafe" suffix in the MetricSeries.GetCurrentAggregateUnsafe() method. + // We added it to underline the need for two important considerations when using this method: + // a) It may return proper objects and nulls in a poorly predictable way: + // For performance reasons, we only create internal aggregators if there is any data to aggregate. + // Consider a situation where you tracked some values for a Measurement metric. So GetCurrentAggregateUnsafe() + // returns a valid object. At any time, the aggregation period (1 minute) could complete. The aggregate + // will be "snapped" and sent to the cloud. Now there is no more aggregate until you track more values during + // the ongoing aggregation period. + // b) Aggregator implementations may choose to optimize their multithreaded performance in a way such that the aggregates + // do not always reflect the latest state. Data will be synchronized correctly before being sent to the cloud at the end + // of the aggregation period, but it may be lagging behind a few milliseconds at other times or it may be inconsistent. + // E.g., following a TrackValue(..) invocation the Count statistic of an aggregate may already be updated, but its Sum + // statistic may not yet be updated. These errors are small and not statistically significant. However you should use + // unsafe aggregates for what they are - statistical summaries, rather than exact counts. + } } private static void DisplayMostPopularBook(MetricAggregate mostPopularBookKind) @@ -388,132 +403,141 @@ public static void Exec() // Recall from an earlier example that a metric can be configured for aggregation as a Measurement or as an Accumulator. // A strong architectural conviction of this Metrics SDK is that metrics tracking and metrics aggregation are distinct concepts // that must be kept separate. This means that a metric is ALWAYS tracked in the same way: + using (var config = TelemetryConfiguration.CreateDefault()) + { + TelemetryClient client = new TelemetryClient(config); + Metric anyKindOfMetric = client.GetMetric("..."); - TelemetryClient client = new TelemetryClient(); - Metric anyKindOfMetric = client.GetMetric("..."); - - anyKindOfMetric.TrackValue(42); + anyKindOfMetric.TrackValue(42); - // If you want to affect the way a metric is aggregated, you need to do this in the one place where the metric is initialized: + // If you want to affect the way a metric is aggregated, you need to do this in the one place where the metric is initialized: - Metric measurementMetric = client.GetMetric("Items Processed per Minute", MetricConfigurations.Common.Measurement()); + Metric measurementMetric = client.GetMetric("Items Processed per Minute", + MetricConfigurations.Common.Measurement()); - // Using the Microsoft.ApplicationInsights.Metrics.Extensions package: - // Metric accumulatorMetric = client.GetMetric("Items in a Data Structure", MetricConfigurations.Common.Accumulator()); + // Using the Microsoft.ApplicationInsights.Metrics.Extensions package: + // Metric accumulatorMetric = client.GetMetric("Items in a Data Structure", MetricConfigurations.Common.Accumulator()); - // Using a private implementation: - Metric accumulatorMetric = client.GetMetric( + // Using a private implementation: + Metric accumulatorMetric = client.GetMetric( "Items in a Data Structure", new MetricConfiguration( - 1000, - 100, - new Microsoft.ApplicationInsights.Metrics.TestUtility.MetricSeriesConfigurationForTestingAccumulatorBehavior())); + 1000, + 100, + new Microsoft.ApplicationInsights.Metrics.TestUtility. + MetricSeriesConfigurationForTestingAccumulatorBehavior())); - measurementMetric.TrackValue(10); - measurementMetric.TrackValue(20); - accumulatorMetric.TrackValue(1); - accumulatorMetric.TrackValue(-1); + measurementMetric.TrackValue(10); + measurementMetric.TrackValue(20); + accumulatorMetric.TrackValue(1); + accumulatorMetric.TrackValue(-1); - // Note that this is an important and intentional difference to some other metric aggregation libraries - // that declare a strongly typed metric object class for different aggregators. + // Note that this is an important and intentional difference to some other metric aggregation libraries + // that declare a strongly typed metric object class for different aggregators. - // If you prefer not to cache the metric reference, you can simply avoid specifying the metric configuration in all except the first call. - // However, you MUST specify a configuration when you initialize the metric for the first time, or we will assume a Measurement. - // E.g., all three of accumulatorMetric2, accumulatorMetric2a and accumulatorMetric2b below are Accumulators. - // (In fact, they are all references to the same object.) + // If you prefer not to cache the metric reference, you can simply avoid specifying the metric configuration in all except the first call. + // However, you MUST specify a configuration when you initialize the metric for the first time, or we will assume a Measurement. + // E.g., all three of accumulatorMetric2, accumulatorMetric2a and accumulatorMetric2b below are Accumulators. + // (In fact, they are all references to the same object.) - // Using the Microsoft.ApplicationInsights.Metrics.Extensions package: - //Metric accumulatorMetric2 = client.GetMetric("Items in a Data Structure 2", MetricConfigurations.Common.Accumulator()); + // Using the Microsoft.ApplicationInsights.Metrics.Extensions package: + //Metric accumulatorMetric2 = client.GetMetric("Items in a Data Structure 2", MetricConfigurations.Common.Accumulator()); - // Using a private implementation: - Metric accumulatorMetric2 = client.GetMetric( + // Using a private implementation: + Metric accumulatorMetric2 = client.GetMetric( "Items in a Data Structure", new MetricConfiguration( - 1000, - 100, - new Microsoft.ApplicationInsights.Metrics.TestUtility.MetricSeriesConfigurationForTestingAccumulatorBehavior())); + 1000, + 100, + new Microsoft.ApplicationInsights.Metrics.TestUtility. + MetricSeriesConfigurationForTestingAccumulatorBehavior())); - Metric accumulatorMetric2a = client.GetMetric("Items in a Data Structure 2"); - Metric accumulatorMetric2b = client.GetMetric("Items in a Data Structure 2", metricConfiguration: null); + Metric accumulatorMetric2a = client.GetMetric("Items in a Data Structure 2"); + Metric accumulatorMetric2b = client.GetMetric("Items in a Data Structure 2", metricConfiguration: null); - // On contrary, metric3 and metric3a are Measurements, because no configuration was specified during the first call: + // On contrary, metric3 and metric3a are Measurements, because no configuration was specified during the first call: - Metric metric3 = client.GetMetric("Metric 3"); - Metric metric3a = client.GetMetric("Metric 3", metricConfiguration: null); + Metric metric3 = client.GetMetric("Metric 3"); + Metric metric3a = client.GetMetric("Metric 3", metricConfiguration: null); - // Be careful: If you specify inconsistent metric configurations, you will get an exception: + // Be careful: If you specify inconsistent metric configurations, you will get an exception: - try - { - Metric accumulatorMetric2c = client.GetMetric("Items in a Data Structure 2", MetricConfigurations.Common.Measurement()); - } - catch(ArgumentException) - { - client.TrackTrace( - "A Metric with the specified Id and dimension names already exists, but it has a configuration" - + " that is different from the specified configuration. You may not change configurations once a" - + " metric was created for the first time. Either specify the same configuration every time, or" - + " specify 'null' during every invocation except the first one. 'Null' will match against any" - + " previously specified configuration when retrieving existing metrics, or fall back to" - + " MetricConfigurations.Common.Measurement() when creating new metrics.", - TraceSeveretyLevel.Error); - } + try + { + Metric accumulatorMetric2c = client.GetMetric("Items in a Data Structure 2", + MetricConfigurations.Common.Measurement()); + } + catch (ArgumentException) + { + client.TrackTrace( + "A Metric with the specified Id and dimension names already exists, but it has a configuration" + + " that is different from the specified configuration. You may not change configurations once a" + + " metric was created for the first time. Either specify the same configuration every time, or" + + " specify 'null' during every invocation except the first one. 'Null' will match against any" + + " previously specified configuration when retrieving existing metrics, or fall back to" + + " MetricConfigurations.Common.Measurement() when creating new metrics.", + TraceSeveretyLevel.Error); + } - // *** CUSTOM METRIC CONFIGURATIONS *** - - // Above we have seen two fixed presets for metric configurations: MetricConfigurations.Common.Measurement() and MetricConfigurations.Common.Accumulator(). - // Both are static objects of class MetricConfiguration. - // You can provide your own implementations of IMetricSeriesConfiguration which is used by MetricConfiguration if you - // want to implement your own custom aggregators; that is covered elsewhere. - // Here, let's focus on creating your own instances of MetricConfiguration to configure more options. - // MetricConfiguration ctor takes some options on how to manage different series within the respective metric and an - // object of class MetricSeriesConfigurationForMeasurement : IMetricSeriesConfiguration that specifies aggregation behavior for - // each individual series of the metric: - - Metric customConfiguredMeasurement = client.GetMetric( - "Custom Metric 1", - new MetricConfiguration( - seriesCountLimit: 1000, - valuesPerDimensionLimit: 100, - seriesConfig: new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false))); - - // seriesCountLimit is the max total number of series the metric can contain before TrackValue(..) and TryGetDataSeries(..) stop - // creating new data series and start returning false. - // valuesPerDimensionLimit limits the number of distinct values per dimension in a similar manner. - // usePersistentAggregation specifies whether the aggregator for each time series will be replaced at the end of each aggregation cycle (false) - // or not (true). This corresponds to the Measurement and the Accumulator aggregations respectively. - // restrictToUInt32Values can be used to force a metric to consume non-negtive integer values only. Certain ono-negative-integer-only - // auto-collected system metrics are stored in the cloud in an optimized, more efficient manner. Custom metrics are currently always - // stored as doubles. - - // In fact, the above customConfiguredMeasurement is how MetricConfigurations.Common.Measurement() is defined by default. - - // If you want to change some of the above configuration values for all metrics in your application without the need to specify - // a custom configuration every time, you can do so by using the MetricConfigurations.Common.SetDefaultForXxx(..) methods. - // Note that this will only affect metrics created after the change: - - Metric someMeasurement1 = client.GetMetric("Some Measurement 1", MetricConfigurations.Common.Measurement()); - - MetricConfigurations.Common.SetDefaultForMeasurement( - new MetricConfigurationForMeasurement( - seriesCountLimit: 10000, - valuesPerDimensionLimit: 5000, - seriesConfig: new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false))); - - Metric someMeasurement2 = client.GetMetric("Some Measurement 2", MetricConfigurations.Common.Measurement()); - - // someMeasurement1 has SeriesCountLimit = 1000 and ValuesPerDimensionLimit = 100. - // someMeasurement2 has SeriesCountLimit = 10000 and ValuesPerDimensionLimit = 5000. - - try - { - Metric someMeasurement1a = client.GetMetric("Some Measurement 1", MetricConfigurations.Common.Measurement()); - } - catch(ArgumentException) - { - // This exception will always occur because the configuration object behind MetricConfigurations.Common.Measurement() - // has changed when MetricConfigurations.FutureDefaults when was modified. + // *** CUSTOM METRIC CONFIGURATIONS *** + + // Above we have seen two fixed presets for metric configurations: MetricConfigurations.Common.Measurement() and MetricConfigurations.Common.Accumulator(). + // Both are static objects of class MetricConfiguration. + // You can provide your own implementations of IMetricSeriesConfiguration which is used by MetricConfiguration if you + // want to implement your own custom aggregators; that is covered elsewhere. + // Here, let's focus on creating your own instances of MetricConfiguration to configure more options. + // MetricConfiguration ctor takes some options on how to manage different series within the respective metric and an + // object of class MetricSeriesConfigurationForMeasurement : IMetricSeriesConfiguration that specifies aggregation behavior for + // each individual series of the metric: + + Metric customConfiguredMeasurement = client.GetMetric( + "Custom Metric 1", + new MetricConfiguration( + seriesCountLimit: 1000, + valuesPerDimensionLimit: 100, + seriesConfig: new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false))); + + // seriesCountLimit is the max total number of series the metric can contain before TrackValue(..) and TryGetDataSeries(..) stop + // creating new data series and start returning false. + // valuesPerDimensionLimit limits the number of distinct values per dimension in a similar manner. + // usePersistentAggregation specifies whether the aggregator for each time series will be replaced at the end of each aggregation cycle (false) + // or not (true). This corresponds to the Measurement and the Accumulator aggregations respectively. + // restrictToUInt32Values can be used to force a metric to consume non-negtive integer values only. Certain ono-negative-integer-only + // auto-collected system metrics are stored in the cloud in an optimized, more efficient manner. Custom metrics are currently always + // stored as doubles. + + // In fact, the above customConfiguredMeasurement is how MetricConfigurations.Common.Measurement() is defined by default. + + // If you want to change some of the above configuration values for all metrics in your application without the need to specify + // a custom configuration every time, you can do so by using the MetricConfigurations.Common.SetDefaultForXxx(..) methods. + // Note that this will only affect metrics created after the change: + + Metric someMeasurement1 = + client.GetMetric("Some Measurement 1", MetricConfigurations.Common.Measurement()); + + MetricConfigurations.Common.SetDefaultForMeasurement( + new MetricConfigurationForMeasurement( + seriesCountLimit: 10000, + valuesPerDimensionLimit: 5000, + seriesConfig: new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false))); + + Metric someMeasurement2 = + client.GetMetric("Some Measurement 2", MetricConfigurations.Common.Measurement()); + + // someMeasurement1 has SeriesCountLimit = 1000 and ValuesPerDimensionLimit = 100. + // someMeasurement2 has SeriesCountLimit = 10000 and ValuesPerDimensionLimit = 5000. + + try + { + Metric someMeasurement1a = + client.GetMetric("Some Measurement 1", MetricConfigurations.Common.Measurement()); + } + catch (ArgumentException) + { + // This exception will always occur because the configuration object behind MetricConfigurations.Common.Measurement() + // has changed when MetricConfigurations.FutureDefaults when was modified. + } } } } @@ -554,45 +578,52 @@ public static void Exec() // Expert users can choose to manage their metric series directly, rather than using a Metric container object. // In that case they will obtain metric series directly from the MetricManager: - MetricManager metrics = TelemetryConfiguration.Active.GetMetricManager(); - - MetricSeries requestSize = metrics.CreateNewSeries( - "Example Metrics", - "Size of Service Resquests", - new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false)); - - requestSize.TrackValue(256); - requestSize.TrackValue(314); - requestSize.TrackValue(189); - - // Note that MetricManager.CreateNewSeries(..) will ALWAYS create a new metric series. It is your responsibility to keep a reference - // to it so that you can access it later. If you do not want to worry about keeping that reference, just use Metric. - - // If you choose to useMetricManager directly, you can specify the dimension names and values associated with a new metric series. - // Note how dimensions can be specified as a dictionary or as an array. On contrary to the Metric class APIs, this approach does not - // take care of series capping and dimension capping. You need to take care of it yourself. - - MetricSeries purpleCowsSold = metrics.CreateNewSeries( - "Example Metrics", - "Animals Sold", - new Dictionary() { ["Species"] = "Cows", ["Color"] = "Purple" }, - new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false)); - - MetricSeries yellowHorsesSold = metrics.CreateNewSeries( - "Example Metrics", - "Animals Sold", - new[] { new KeyValuePair("Species", "Horses"), new KeyValuePair("Color", "Yellow") }, - new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false)); + using (var config = TelemetryConfiguration.CreateDefault()) + { + MetricManager metrics = config.GetMetricManager(); + + MetricSeries requestSize = metrics.CreateNewSeries( + "Example Metrics", + "Size of Service Resquests", + new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false)); + + requestSize.TrackValue(256); + requestSize.TrackValue(314); + requestSize.TrackValue(189); + + // Note that MetricManager.CreateNewSeries(..) will ALWAYS create a new metric series. It is your responsibility to keep a reference + // to it so that you can access it later. If you do not want to worry about keeping that reference, just use Metric. + + // If you choose to useMetricManager directly, you can specify the dimension names and values associated with a new metric series. + // Note how dimensions can be specified as a dictionary or as an array. On contrary to the Metric class APIs, this approach does not + // take care of series capping and dimension capping. You need to take care of it yourself. + + MetricSeries purpleCowsSold = metrics.CreateNewSeries( + "Example Metrics", + "Animals Sold", + new Dictionary() {["Species"] = "Cows", ["Color"] = "Purple"}, + new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false)); + + MetricSeries yellowHorsesSold = metrics.CreateNewSeries( + "Example Metrics", + "Animals Sold", + new[] + { + new KeyValuePair("Species", "Horses"), + new KeyValuePair("Color", "Yellow") + }, + new MetricSeriesConfigurationForMeasurement(restrictToUInt32Values: false)); - purpleCowsSold.TrackValue(42); - yellowHorsesSold.TrackValue(132); + purpleCowsSold.TrackValue(42); + yellowHorsesSold.TrackValue(132); - // *** FLUSHING *** + // *** FLUSHING *** - // MetricManager also allows you to flush all your metric aggregators and send the current aggregates to the cloud without waiting - // for the end of the ongoing aggregation period: + // MetricManager also allows you to flush all your metric aggregators and send the current aggregates to the cloud without waiting + // for the end of the ongoing aggregation period: - TelemetryConfiguration.Active.GetMetricManager().Flush(); + config.GetMetricManager().Flush(); + } } } } @@ -619,73 +650,78 @@ public static void Exec() // Recall the problem description: // Metric aggregates sent by the below "Special Operation Request Size"-metric will NOT have their Context.Operation.Name set to "Special Operation". - - TelemetryClient specialClient = new TelemetryClient(); - specialClient.Context.Operation.Name = "Special Operation"; - specialClient.GetMetric("Special Operation Request Size").TrackValue(GetCurrentRequestSize()); - - // The reason for that is that by default, metrics are aggregated at the scope of the TelemetryConfiguration pipeline and not at the scope - // of a particular TelemetryClient. This is because of a very common pattern for Application Insights users where a TelemetryClient is created - // for a small scope. For example: - + using (var config = TelemetryConfiguration.CreateDefault()) { - // ... - (new TelemetryClient()).TrackEvent("Something Interesting Happened"); - // ... - } + TelemetryClient specialClient = new TelemetryClient(config); + specialClient.Context.Operation.Name = "Special Operation"; + specialClient.GetMetric("Special Operation Request Size").TrackValue(GetCurrentRequestSize()); + + // The reason for that is that by default, metrics are aggregated at the scope of the TelemetryConfiguration pipeline and not at the scope + // of a particular TelemetryClient. This is because of a very common pattern for Application Insights users where a TelemetryClient is created + // for a small scope. For example: - { - try { - RunSomeCode(); + // ... + (new TelemetryClient(config)).TrackEvent("Something Interesting Happened"); + // ... } - catch (Exception apEx) + { - (new TelemetryClient()).TrackException(apEx); + try + { + RunSomeCode(); + } + catch (Exception apEx) + { + (new TelemetryClient(config)).TrackException(apEx); + } } - } - - // ...and so on. - // We wanted to support this pattern and to allow users to write code like this: - - { - // ... - (new TelemetryClient()).GetMetric("Temperature").TrackValue(36.6); - // --- - } - - { - // ... - (new TelemetryClient()).GetMetric("Temperature").TrackValue(39.1); - // --- - } - - // In this case the expected behavior is that these values are aggregated together into a single aggregate with Count = 2, Sum = 75.7 and so on. - // In order to achieve that, we use a single MetricManager to create all the respective metric series. This manager is attached to the - // TelemetryConfiguration that stands behind a TelemetryClient. This ensures that the two (new TelemetryClient()).GetMetric("Temperature") statements - // above return the same Metric object. - // However, if different TelemetryClient instances return the name Metric instance, then what client's Context should the Metric respect? - // To avoid confusion, it respects none. - - // The best workaround for this circumstance was mentioned in a previous example - use the special dimension names in the MetricDimensionNames class. - // However, sometimes it is inconvenient. For example, if you already created a cached TelemetryClient for a specific scope and set some custom - // Context properties. - // It is actually possible to create a metric that is only scoped to a single TelemetryClient instance. This will cause the creation of a special - // MetricManager instance at the scope of that one TelemetryClient. We highly recommend using this feature with restraint, as a MetricManager can - // use a non-trivial amount of resources, including separate aggregators for each metric series and a managed thread for sending aggregated telemetry. - // Here how this works: - TelemetryClient operationClient = new TelemetryClient(); - operationClient.Context.Operation.Name = "Operation XYZ"; // This client will only send telemetry related to a specific operation. - operationClient.InstrumentationKey = "05B5093A-F137-4A68-B826-A950CB68C68F"; // This client sends telemetry to a special Application Insights component. + // ...and so on. + // We wanted to support this pattern and to allow users to write code like this: - Metric operationRequestSize = operationClient.GetMetric("XYZ Request Size", MetricConfigurations.Common.Measurement(), MetricAggregationScope.TelemetryClient); + { + // ... + (new TelemetryClient(config)).GetMetric("Temperature").TrackValue(36.6); + // --- + } - int requestSize = GetCurrentRequestSize(); - operationRequestSize.TrackValue(306000); + { + // ... + (new TelemetryClient(config)).GetMetric("Temperature").TrackValue(39.1); + // --- + } - // Note the last parameter to GetMetric: MetricAggregationScope.TelemetryClient. This instructed the GetMetric API not to use the metric - // manager at the TelemetryConfiguration scope, but to create and use a metric manager at the respective client's scope instead. + // In this case the expected behavior is that these values are aggregated together into a single aggregate with Count = 2, Sum = 75.7 and so on. + // In order to achieve that, we use a single MetricManager to create all the respective metric series. This manager is attached to the + // TelemetryConfiguration that stands behind a TelemetryClient. This ensures that the two (new TelemetryClient()).GetMetric("Temperature") statements + // above return the same Metric object. + // However, if different TelemetryClient instances return the name Metric instance, then what client's Context should the Metric respect? + // To avoid confusion, it respects none. + + // The best workaround for this circumstance was mentioned in a previous example - use the special dimension names in the MetricDimensionNames class. + // However, sometimes it is inconvenient. For example, if you already created a cached TelemetryClient for a specific scope and set some custom + // Context properties. + // It is actually possible to create a metric that is only scoped to a single TelemetryClient instance. This will cause the creation of a special + // MetricManager instance at the scope of that one TelemetryClient. We highly recommend using this feature with restraint, as a MetricManager can + // use a non-trivial amount of resources, including separate aggregators for each metric series and a managed thread for sending aggregated telemetry. + // Here how this works: + + TelemetryClient operationClient = new TelemetryClient(config); + operationClient.Context.Operation.Name = + "Operation XYZ"; // This client will only send telemetry related to a specific operation. + operationClient.InstrumentationKey = + "05B5093A-F137-4A68-B826-A950CB68C68F"; // This client sends telemetry to a special Application Insights component. + + Metric operationRequestSize = operationClient.GetMetric("XYZ Request Size", + MetricConfigurations.Common.Measurement(), MetricAggregationScope.TelemetryClient); + + int requestSize = GetCurrentRequestSize(); + operationRequestSize.TrackValue(306000); + + // Note the last parameter to GetMetric: MetricAggregationScope.TelemetryClient. This instructed the GetMetric API not to use the metric + // manager at the TelemetryConfiguration scope, but to create and use a metric manager at the respective client's scope instead. + } } private static void RunSomeCode() @@ -739,9 +775,9 @@ public static void ExecA() // to specify a telemetry client using dependency injection. The code for the class is listed below. // In a production application the class will probably be instantiated and called like this: - + using (var config = TelemetryConfiguration.CreateDefault()) { - ServiceClassA serviceA = new ServiceClassA(new TelemetryClient()); + ServiceClassA serviceA = new ServiceClassA(new TelemetryClient(config)); serviceA.SellPurpleDucks(42); } @@ -810,36 +846,37 @@ public static void ExecB() // In a production application the class will probably be instantiated and called like this: + using (var config = TelemetryConfiguration.CreateDefault()) { - ServiceClassB serviceB = new ServiceClassB(); - serviceB.SellPurpleDucks(42); - } - - // Here is the unit test: + { + ServiceClassB serviceB = new ServiceClassB(config); + serviceB.SellPurpleDucks(42); + } + // Here is the unit test: - { // Do not forget to set the InstrumentationKey to some value, otherwise the pipeline will not send any telemetry to the channel. - TelemetryConfiguration.Active.InstrumentationKey = Guid.NewGuid().ToString("D"); + config.InstrumentationKey = Guid.NewGuid().ToString("D"); // This approach is more widely applicable, and does not require to prepare your code for injection of a telemetry client. // However, a significant drawback is that in this model different unit tests can interfere with each other via the static default // telemetry pipeline. Such interference may be non-trivial. E.g., for this simple test, we need to flush out all the tracked values // from the code that just run. This will flush out all Measurements, but not Accumulators, since they persist between flushes. // This can make unit testing with this method quite complex. - TelemetryConfiguration.Active.GetMetricManager().Flush(); - (new TelemetryClient(TelemetryConfiguration.Active)).Flush(); + config.GetMetricManager().Flush(); + (new TelemetryClient(config)).Flush(); // Create the test pipeline and client. StubTelemetryChannel telemetryCollector = new StubTelemetryChannel(); - TelemetryConfiguration.Active.TelemetryChannel = telemetryCollector; - TelemetryConfiguration.Active.InstrumentationKey = Guid.NewGuid().ToString("D"); + config.TelemetryChannel = telemetryCollector; + config.InstrumentationKey = Guid.NewGuid().ToString("D"); // Invoke method being tested: - ServiceClassB serviceB = new ServiceClassB(); - serviceB.SellPurpleDucks(42); - + { + ServiceClassB serviceB = new ServiceClassB(config); + serviceB.SellPurpleDucks(42); + } // Flushing the MetricManager is particularly important since the aggregation period of 1 minute has just started: - TelemetryConfiguration.Active.GetMetricManager().Flush(); + config.GetMetricManager().Flush(); // As mentioned, tests using this approach interfere with each other. // For example, when running all the examples here after each other, accumulators from previous examples are still associated with the @@ -908,17 +945,19 @@ public void SellPurpleDucks(int count) internal class ServiceClassB { - public ServiceClassB() + private readonly TelemetryConfiguration configuration; + public ServiceClassB(TelemetryConfiguration configuration) { + this.configuration = configuration; } public void SellPurpleDucks(int count) { // Do some stuff #1... - (new TelemetryClient()).TrackTrace("Stuff #1 completed", TraceSeveretyLevel.Information); + (new TelemetryClient(configuration)).TrackTrace("Stuff #1 completed", TraceSeveretyLevel.Information); // Do more stuff... - (new TelemetryClient()).GetMetric("Ducks Sold", "Color").TrackValue(count, "Purple"); + (new TelemetryClient(configuration)).GetMetric("Ducks Sold", "Color").TrackValue(count, "Purple"); } } @@ -995,7 +1034,7 @@ namespace User.Namespace.Example06c using Microsoft.VisualStudio.TestTools.UnitTesting; - using TraceSeveretyLevel = Microsoft.ApplicationInsights.DataContracts.SeverityLevel; + using TraceSeverityLevel = Microsoft.ApplicationInsights.DataContracts.SeverityLevel; /// /// In this example we discuss how to write unit tests that validate that metrics are sent correctly @@ -1032,98 +1071,129 @@ public static void ExecC() // In the context of testing, users can use "virtual time", i.e. they can specify any timestamps in a test that // runs only for milliseconds, thus testing various timing scenarios. - - DateTimeOffset testStartTime = new DateTimeOffset(2017, 11, 1, 13, 0, 0, TimeSpan.FromHours(8)); - - // In order to use custom aggregation cycles and other advanced metrics features, import the following namespace: - // using Microsoft.ApplicationInsights.Metrics.Extensibility; - - // By default all non-default aggregation cycles are inactive. To activate the custom cycle, request the custom cycle aggregates: - - MetricManager defaultMetricManager = TelemetryConfiguration.Active.GetMetricManager(); - AggregationPeriodSummary lastCycle = defaultMetricManager.StartOrCycleAggregators( - MetricAggregationCycleKind.Custom, - testStartTime, - ExcludeAccumulatorsFromPreviousTestsFilter.Instance); - - // If the cycle was inactive so far, it will be started up and aggregation into the cycle will begin. Other cycles will be unaffected. - // Since this was the first invocation, the received AggregationPeriodSummary is empty: - - Assert.AreEqual(0, lastCycle.NonpersistentAggregates.Count); - - // Now we can call the method being tested. - - ServiceClassC serviceC = new ServiceClassC(); - serviceC.SellPurpleDucks(42); - - // Now we can pull the data again. Let us pretend that 1 full "virtual" minute has passed: - - lastCycle = defaultMetricManager.StartOrCycleAggregators( - MetricAggregationCycleKind.Custom, - testStartTime.AddMinutes(1), - ExcludeAccumulatorsFromPreviousTestsFilter.Instance); - - // Now we can verify that metrics were tracked correctly: - - Assert.AreEqual(1, lastCycle.NonpersistentAggregates.Count, "One Measurement should be tracked"); - Assert.AreEqual(0, lastCycle.PersistentAggregates.Count, "No Accumulators should be tracked"); - - Assert.AreEqual("Ducks Sold", lastCycle.NonpersistentAggregates[0].MetricId); - Assert.AreEqual(MetricConfigurations.Common.Measurement().Constants().AggregateKindMoniker, lastCycle.NonpersistentAggregates[0].AggregationKindMoniker); - Assert.AreEqual(testStartTime, lastCycle.NonpersistentAggregates[0].AggregationPeriodStart); - Assert.AreEqual(TimeSpan.FromMinutes(1), lastCycle.NonpersistentAggregates[0].AggregationPeriodDuration); - Assert.AreEqual(1, lastCycle.NonpersistentAggregates[0].Dimensions.Count); - Assert.AreEqual("Purple", lastCycle.NonpersistentAggregates[0].Dimensions["Color"]); - Assert.AreEqual(1, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Count, -1)); - Assert.AreEqual(42, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, -1)); - Assert.AreEqual(42, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Min, -1)); - Assert.AreEqual(42, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Max, -1)); - Assert.AreEqual(0, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.StdDev, -1)); - - // Note that because "Ducks Sold" is a Measurement, and because we cycled the custom aggregators, the current aggregator is now empty. - // However, if it was an Accumulator, it would keep the values tracked thus far. To help differentiate between these two cases, Measurement-like - // aggregates are contained within AggregationPeriodSummary.NonpersistentAggregates and Accumulator-like aggregates are contained within - // AggregationPeriodSummary.PersistentAggregates. - - // Let's call the tested API again, now twice: - - serviceC.SellPurpleDucks(11); - serviceC.SellPurpleDucks(12); - - // Since we are now done, we will gracefully shut down the custom aggregation cycle. We will receive the last aggregates: - - lastCycle = defaultMetricManager.StopAggregators(MetricAggregationCycleKind.Custom, testStartTime.AddMinutes(2)); - - Assert.AreEqual(1, lastCycle.NonpersistentAggregates.Count, "One Measurement should be tracked (with two values)"); - Assert.AreEqual(0, lastCycle.PersistentAggregates.Count, "No Accumulators should be tracked"); - - Assert.AreEqual("Ducks Sold", lastCycle.NonpersistentAggregates[0].MetricId); - Assert.AreEqual(MetricConfigurations.Common.Measurement().Constants().AggregateKindMoniker, lastCycle.NonpersistentAggregates[0].AggregationKindMoniker); - Assert.AreEqual(testStartTime.AddMinutes(1), lastCycle.NonpersistentAggregates[0].AggregationPeriodStart); - Assert.AreEqual(TimeSpan.FromMinutes(1), lastCycle.NonpersistentAggregates[0].AggregationPeriodDuration); - Assert.AreEqual(1, lastCycle.NonpersistentAggregates[0].Dimensions.Count); - Assert.AreEqual("Purple", lastCycle.NonpersistentAggregates[0].Dimensions["Color"]); - Assert.AreEqual(2, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Count, -1)); - Assert.AreEqual(23, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, -1)); - Assert.AreEqual(11, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Min, -1)); - Assert.AreEqual(12, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Max, -1)); - Assert.AreEqual(0.5, lastCycle.NonpersistentAggregates[0].GetDataValue(MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.StdDev, -1)); + using (var config = TelemetryConfiguration.CreateDefault()) + { + DateTimeOffset testStartTime = new DateTimeOffset(2017, 11, 1, 13, 0, 0, TimeSpan.FromHours(8)); + + // In order to use custom aggregation cycles and other advanced metrics features, import the following namespace: + // using Microsoft.ApplicationInsights.Metrics.Extensibility; + + // By default all non-default aggregation cycles are inactive. To activate the custom cycle, request the custom cycle aggregates: + + MetricManager defaultMetricManager = config.GetMetricManager(); + AggregationPeriodSummary lastCycle = defaultMetricManager.StartOrCycleAggregators( + MetricAggregationCycleKind.Custom, + testStartTime, + ExcludeAccumulatorsFromPreviousTestsFilter.Instance); + + // If the cycle was inactive so far, it will be started up and aggregation into the cycle will begin. Other cycles will be unaffected. + // Since this was the first invocation, the received AggregationPeriodSummary is empty: + + Assert.AreEqual(0, lastCycle.NonpersistentAggregates.Count); + + // Now we can call the method being tested. + + ServiceClassC serviceC = new ServiceClassC(config); + serviceC.SellPurpleDucks(42); + + // Now we can pull the data again. Let us pretend that 1 full "virtual" minute has passed: + + lastCycle = defaultMetricManager.StartOrCycleAggregators( + MetricAggregationCycleKind.Custom, + testStartTime.AddMinutes(1), + ExcludeAccumulatorsFromPreviousTestsFilter.Instance); + + // Now we can verify that metrics were tracked correctly: + + Assert.AreEqual(1, lastCycle.NonpersistentAggregates.Count, "One Measurement should be tracked"); + Assert.AreEqual(0, lastCycle.PersistentAggregates.Count, "No Accumulators should be tracked"); + + Assert.AreEqual("Ducks Sold", lastCycle.NonpersistentAggregates[0].MetricId); + Assert.AreEqual(MetricConfigurations.Common.Measurement().Constants().AggregateKindMoniker, + lastCycle.NonpersistentAggregates[0].AggregationKindMoniker); + Assert.AreEqual(testStartTime, lastCycle.NonpersistentAggregates[0].AggregationPeriodStart); + Assert.AreEqual(TimeSpan.FromMinutes(1), + lastCycle.NonpersistentAggregates[0].AggregationPeriodDuration); + Assert.AreEqual(1, lastCycle.NonpersistentAggregates[0].Dimensions.Count); + Assert.AreEqual("Purple", lastCycle.NonpersistentAggregates[0].Dimensions["Color"]); + Assert.AreEqual(1, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Count, -1)); + Assert.AreEqual(42, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, -1)); + Assert.AreEqual(42, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Min, -1)); + Assert.AreEqual(42, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Max, -1)); + Assert.AreEqual(0, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.StdDev, -1)); + + // Note that because "Ducks Sold" is a Measurement, and because we cycled the custom aggregators, the current aggregator is now empty. + // However, if it was an Accumulator, it would keep the values tracked thus far. To help differentiate between these two cases, Measurement-like + // aggregates are contained within AggregationPeriodSummary.NonpersistentAggregates and Accumulator-like aggregates are contained within + // AggregationPeriodSummary.PersistentAggregates. + + // Let's call the tested API again, now twice: + + serviceC.SellPurpleDucks(11); + serviceC.SellPurpleDucks(12); + + // Since we are now done, we will gracefully shut down the custom aggregation cycle. We will receive the last aggregates: + + lastCycle = defaultMetricManager.StopAggregators(MetricAggregationCycleKind.Custom, + testStartTime.AddMinutes(2)); + + Assert.AreEqual(1, lastCycle.NonpersistentAggregates.Count, + "One Measurement should be tracked (with two values)"); + Assert.AreEqual(0, lastCycle.PersistentAggregates.Count, "No Accumulators should be tracked"); + + Assert.AreEqual("Ducks Sold", lastCycle.NonpersistentAggregates[0].MetricId); + Assert.AreEqual(MetricConfigurations.Common.Measurement().Constants().AggregateKindMoniker, + lastCycle.NonpersistentAggregates[0].AggregationKindMoniker); + Assert.AreEqual(testStartTime.AddMinutes(1), + lastCycle.NonpersistentAggregates[0].AggregationPeriodStart); + Assert.AreEqual(TimeSpan.FromMinutes(1), + lastCycle.NonpersistentAggregates[0].AggregationPeriodDuration); + Assert.AreEqual(1, lastCycle.NonpersistentAggregates[0].Dimensions.Count); + Assert.AreEqual("Purple", lastCycle.NonpersistentAggregates[0].Dimensions["Color"]); + Assert.AreEqual(2, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Count, -1)); + Assert.AreEqual(23, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Sum, -1)); + Assert.AreEqual(11, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Min, -1)); + Assert.AreEqual(12, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.Max, -1)); + Assert.AreEqual(0.5, + lastCycle.NonpersistentAggregates[0].GetDataValue( + MetricConfigurations.Common.Measurement().Constants().AggregateKindDataKeys.StdDev, -1)); + } } } internal class ServiceClassC { - public ServiceClassC() + private readonly TelemetryConfiguration configuration; + public ServiceClassC(TelemetryConfiguration configuration) { + this.configuration = configuration; } public void SellPurpleDucks(int count) { // Do some stuff #1... - (new TelemetryClient()).TrackTrace("Stuff #1 completed", TraceSeveretyLevel.Information); + (new TelemetryClient(configuration)).TrackTrace("Stuff #1 completed", TraceSeverityLevel.Information); // Do more stuff... - (new TelemetryClient()).GetMetric("Ducks Sold", "Color").TrackValue(count, "Purple"); + (new TelemetryClient(configuration)).GetMetric("Ducks Sold", "Color").TrackValue(count, "Purple"); } } @@ -1198,5 +1268,3 @@ public void Example06() } } } - -#pragma warning restore CA1716, 612, 618 // Namespace naming, obsolete TelemetryConfigration.Active \ No newline at end of file diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/TelemetryConfigurationExtensionsTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/TelemetryConfigurationExtensionsTests.cs index 71fe8b59c3..63f8b48a88 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/TelemetryConfigurationExtensionsTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Metrics/TelemetryConfigurationExtensionsTests.cs @@ -1,6 +1,4 @@ -#pragma warning disable 612, 618 // obsolete TelemetryConfigration.Active - -using System; +using System; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -20,8 +18,8 @@ public class TelemetryConfigurationExtensionsTests [TestMethod] public void Metrics_DefaultPipeline() { - TelemetryConfiguration defaultTelemetryPipeline = TelemetryConfiguration.Active; - //using (defaultTelemetryPipeline) + TelemetryConfiguration defaultTelemetryPipeline = TelemetryConfiguration.CreateDefault(); + using (defaultTelemetryPipeline) { Metrics_SpecifiedPipeline(defaultTelemetryPipeline); TestUtil.CompleteDefaultAggregationCycle(defaultTelemetryPipeline.GetMetricManager()); @@ -33,8 +31,8 @@ public void Metrics_DefaultPipeline() [TestMethod] public void Metrics_CustomPipeline() { - TelemetryConfiguration defaultTelemetryPipeline = TelemetryConfiguration.Active; - //using (defaultTelemetryPipeline) + TelemetryConfiguration defaultTelemetryPipeline = TelemetryConfiguration.CreateDefault(); + using (defaultTelemetryPipeline) using (TelemetryConfiguration customTelemetryPipeline1 = TestUtil.CreateAITelemetryConfig()) using (TelemetryConfiguration customTelemetryPipeline2 = TestUtil.CreateAITelemetryConfig()) { @@ -136,4 +134,3 @@ private static void Metrics_SpecifiedPipeline(TelemetryConfiguration telemetryPi //} } } -#pragma warning restore 612, 618 // obsolete TelemetryConfigration.Active \ No newline at end of file diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/Microsoft.ApplicationInsights.Shared.Tests.projitems b/Test/Microsoft.ApplicationInsights.Test/Shared/Microsoft.ApplicationInsights.Shared.Tests.projitems index 6a7ed574c1..71a7b4ca53 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Microsoft.ApplicationInsights.Shared.Tests.projitems +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Microsoft.ApplicationInsights.Shared.Tests.projitems @@ -16,6 +16,7 @@ + @@ -122,8 +123,6 @@ - - diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/StartOperationActivityTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/StartOperationActivityTests.cs index d22d841a45..3bb794ac0c 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/StartOperationActivityTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/StartOperationActivityTests.cs @@ -11,9 +11,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Extensibility.Implementation; using TestFramework; + using Microsoft.ApplicationInsights.Extensibility.W3C; /// - /// This class tests TelemetryClientEzxtensions.StartOperation(TelemetryClient c, Activity a) overload + /// This class tests TelemetryClientExtensions.StartOperation(TelemetryClient c, Activity a) overload /// [TestClass] public class StartOperationActivityTests @@ -31,6 +32,8 @@ public void TestInitialize() configuration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer()); this.telemetryClient = new TelemetryClient(configuration); CallContextHelpers.RestoreOperationContext(null); + + ActivityFormatHelper.EnableW3CFormatInActivity(); } [TestCleanup] @@ -61,6 +64,63 @@ public void BasicStartOperationWithActivity() Assert.AreEqual(telemetry, this.sendItems.Single()); } + [TestMethod] + public void BasicStartOperationWithActivityInScopeOfUnrelatedActivity() + { + var outerActivity = new Activity("foo").Start(); + + var activity = new Activity("name").SetParentId("parentId").AddBaggage("b1", "v1").AddTag("t1", "v1"); + + RequestTelemetry telemetry; + using (var operation = this.telemetryClient.StartOperation(activity)) + { + telemetry = operation.Telemetry; + Assert.AreEqual(activity, Activity.Current); + Assert.AreNotEqual(outerActivity, Activity.Current.Parent); + Assert.IsNotNull(activity.Id); + } + + this.ValidateTelemetry(telemetry, activity); + + Assert.AreEqual(telemetry, this.sendItems.Single()); + Assert.AreEqual(outerActivity, Activity.Current); + + var request = this.sendItems.Single() as RequestTelemetry; + Assert.IsNotNull(request); + Assert.AreEqual(activity.TraceId.ToHexString(), request.Context.Operation.Id); + Assert.AreEqual($"|{activity.TraceId.ToHexString()}.{activity.SpanId.ToHexString()}.", request.Id); + Assert.AreEqual("parentId", request.Context.Operation.ParentId); + } + + [TestMethod] + public void BasicStartOperationWithStartedActivityInScopeOfUnrelatedActivity() + { + var outerActivity = new Activity("foo").Start(); + + // this is not right to give started Activity to StartOperation, but nothing terrible should happen + // except it won't be possible to restore original context after StartOperation completes + var activity = new Activity("name").SetParentId("parentId").AddBaggage("b1", "v1").AddTag("t1", "v1").Start(); + + RequestTelemetry telemetry; + using (var operation = this.telemetryClient.StartOperation(activity)) + { + telemetry = operation.Telemetry; + Assert.AreEqual(activity, Activity.Current); + Assert.AreNotEqual(outerActivity, Activity.Current.Parent); + Assert.IsNotNull(activity.Id); + } + + this.ValidateTelemetry(telemetry, activity); + + Assert.AreEqual(telemetry, this.sendItems.Single()); + Assert.IsNull(Activity.Current); + + var request = this.sendItems.Single() as RequestTelemetry; + Assert.IsNotNull(request); + Assert.AreEqual(activity.TraceId.ToHexString(), request.Context.Operation.Id); + Assert.AreEqual($"|{activity.TraceId.ToHexString()}.{activity.SpanId.ToHexString()}.", request.Id); + Assert.AreEqual("parentId", request.Context.Operation.ParentId); + } /// /// Invalid Usage! Tests that if Activity is started, StartOperation still works and does not crash. @@ -272,10 +332,18 @@ private T ProcessWithStartOperation(Activity activity, Activity parentActivit return telemetry; } - private void ValidateTelemetry(T telemetry, Activity activity) where T : OperationTelemetry + private void ValidateTelemetry(T telemetry, Activity activity, bool isW3C = true) where T : OperationTelemetry { Assert.AreEqual(activity.OperationName, telemetry.Name); - Assert.AreEqual(activity.Id, telemetry.Id); + if (isW3C) + { + Assert.AreEqual(W3CUtilities.FormatTelemetryId(activity.TraceId.ToHexString(), activity.SpanId.ToHexString()), telemetry.Id); + } + else + { + Assert.AreEqual(activity.Id, telemetry.Id); + } + Assert.AreEqual(activity.ParentId, telemetry.Context.Operation.ParentId); Assert.AreEqual(activity.RootId, telemetry.Context.Operation.Id); diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientExtensionTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientExtensionTests.cs index 6b4208c9a8..4330961530 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientExtensionTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientExtensionTests.cs @@ -10,23 +10,31 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Extensibility.Implementation; using TestFramework; + using Microsoft.ApplicationInsights.Extensibility.W3C; [TestClass] public class TelemetryClientExtensionTests { + const string NonW3CCompatibleOperationId = "NonCompliantRootId"; + const string W3CCompatibleOperationId = "8ee8641cbdd8dd280d239fa2121c7e4e"; + const string AnyRootId = "ANYID"; + const string AnyParentId = "ANYParentID"; + private TelemetryClient telemetryClient; + private TelemetryConfiguration telemetryConfiguration; private List sendItems; [TestInitialize] public void TestInitialize() { - var configuration = new TelemetryConfiguration(); + this.telemetryConfiguration = new TelemetryConfiguration(); this.sendItems = new List(); - configuration.TelemetryChannel = new StubTelemetryChannel { OnSend = item => this.sendItems.Add(item) }; - configuration.InstrumentationKey = Guid.NewGuid().ToString(); - configuration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer()); - this.telemetryClient = new TelemetryClient(configuration); + telemetryConfiguration.TelemetryChannel = new StubTelemetryChannel { OnSend = item => this.sendItems.Add(item) }; + telemetryConfiguration.InstrumentationKey = Guid.NewGuid().ToString(); + telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer()); + this.telemetryClient = new TelemetryClient(telemetryConfiguration); CallContextHelpers.RestoreOperationContext(null); + ActivityFormatHelper.EnableW3CFormatInActivity(); } [TestCleanup] @@ -55,14 +63,14 @@ public void StartDependencyTrackingReturnsOperationWithInitializedOperationName( } [TestMethod] - public void StartDependencyTrackingReturnsOperationWithInitializedOperationId() + public void StartDependencyTrackingReturnsOperationWithInitializedOperationTelemetryId() { var operation = this.telemetryClient.StartOperation("TestOperationName"); - Assert.IsNotNull(operation.Telemetry.Context.Operation.Id); + Assert.IsNotNull(operation.Telemetry.Id); } [TestMethod] - public void StartDependencyTrackingReturnsOperationWithInitializedOperationRootId() + public void StartDependencyTrackingReturnsOperationWithInitializedOperationId() { var operation = this.telemetryClient.StartOperation("TestOperationName"); Assert.IsNotNull(operation.Telemetry.Context.Operation.Id); @@ -89,6 +97,7 @@ public void StartDependencyTrackingAddsOperationContextStoreToCurrentActivity() Assert.IsNull(Activity.Current); var operation = this.telemetryClient.StartOperation(operationName: null); Assert.IsNotNull(Activity.Current); + Assert.AreEqual(ActivityIdFormat.W3C, Activity.Current.IdFormat); } [TestMethod] @@ -117,8 +126,31 @@ public void UsingWithStopOperationSendsTelemetryAndDisposesOperationItemOnlyOnce } [TestMethod] - public void StartDependencyTrackingHandlesMultipleContextStoresInCurrentActivity() + public void StartDependencyTrackingHandlesMultipleContextStoresInCurrentActivityW3C() { + var operation = this.telemetryClient.StartOperation("OperationName") as OperationHolder; + var currentActivity = Activity.Current; + Assert.AreEqual(operation.Telemetry.Id, W3CUtilities.FormatTelemetryId(currentActivity.TraceId.ToHexString(), currentActivity.SpanId.ToHexString())); + Assert.AreEqual(operation.Telemetry.Context.Operation.Name, this.GetOperationName(currentActivity)); + + var childOperation = this.telemetryClient.StartOperation("OperationName") as OperationHolder; + var childActivity = Activity.Current; + Assert.AreEqual(childOperation.Telemetry.Id, W3CUtilities.FormatTelemetryId(childActivity.TraceId.ToHexString(), childActivity.SpanId.ToHexString())); + Assert.AreEqual(childOperation.Telemetry.Context.Operation.Name, this.GetOperationName(currentActivity)); + + Assert.IsNull(currentActivity.Parent); + Assert.AreEqual(currentActivity, childActivity.Parent); + + this.telemetryClient.StopOperation(childOperation); + Assert.AreEqual(currentActivity, Activity.Current); + this.telemetryClient.StopOperation(operation); + Assert.IsNull(Activity.Current); + } + + [TestMethod] + public void StartDependencyTrackingHandlesMultipleContextStoresInCurrentActivityNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); var operation = this.telemetryClient.StartOperation("OperationName") as OperationHolder; var currentActivity = Activity.Current; Assert.AreEqual(operation.Telemetry.Id, currentActivity.Id); @@ -136,6 +168,8 @@ public void StartDependencyTrackingHandlesMultipleContextStoresInCurrentActivity Assert.AreEqual(currentActivity, Activity.Current); this.telemetryClient.StopOperation(operation); Assert.IsNull(Activity.Current); + + ActivityFormatHelper.EnableW3CFormatInActivity(); } [TestMethod] @@ -205,7 +239,7 @@ public void DisposeOperationAppliesChangesOnActivityDoneAfterStart() } [TestMethod] - public void ContextPropagatesThroughNestedOperations() + public void ContextPropagatesThroughNestedOperationsW3C() { using (this.telemetryClient.StartOperation("OuterRequest")) { @@ -225,7 +259,223 @@ public void ContextPropagatesThroughNestedOperations() } [TestMethod] - public void StartOperationCanOverrideOperationId() + public void ContextPropagatesThroughNestedOperationsNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + + try + { + using (this.telemetryClient.StartOperation("OuterRequest")) + { + using (this.telemetryClient.StartOperation("DependentCall")) + { + } + } + + Assert.AreEqual(2, this.sendItems.Count); + + var requestTelemetry = (RequestTelemetry)this.sendItems[1]; + var dependentTelemetry = (DependencyTelemetry)this.sendItems[0]; + Assert.IsNull(requestTelemetry.Context.Operation.ParentId); + Assert.AreEqual(requestTelemetry.Id, dependentTelemetry.Context.Operation.ParentId); + Assert.AreEqual(requestTelemetry.Context.Operation.Id, dependentTelemetry.Context.Operation.Id); + Assert.AreEqual(requestTelemetry.Context.Operation.Name, dependentTelemetry.Context.Operation.Name); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + + [TestMethod] + public void StartStopRespectsUserProvidedIdsInScopeOfAnotherActivityExplicitIds() + { + var activity = new Activity("foo").Start(); + + var customOperationId = ActivityTraceId.CreateRandom().ToHexString(); + var customParentId = ActivitySpanId.CreateRandom().ToHexString(); + + using (var operation = this.telemetryClient.StartOperation("name", customOperationId, customParentId)) + { + Assert.IsNotNull(Activity.Current); + Assert.AreNotEqual(activity, Activity.Current.Parent); + Assert.AreEqual(customOperationId, Activity.Current.TraceId.ToHexString()); + Assert.AreEqual(customOperationId, operation.Telemetry.Context.Operation.Id); + Assert.AreEqual(customParentId, operation.Telemetry.Context.Operation.ParentId); + } + + Assert.AreEqual(activity, Activity.Current); + Assert.AreEqual(1, this.sendItems.Count); + Assert.IsTrue(this.sendItems.Single() is DependencyTelemetry); + + var dependency = this.sendItems.Single() as DependencyTelemetry; + + Assert.AreEqual(customOperationId, dependency.Context.Operation.Id); + Assert.AreEqual(customParentId, dependency.Context.Operation.ParentId); + } + + [TestMethod] + public void StartStopRespectsUserProvidedIdsInScopeOfAnotherActivityExplicitOperationIdOnly() + { + var activity = new Activity("foo").Start(); + + var customOperationId = ActivityTraceId.CreateRandom().ToHexString(); + + using (var operation = this.telemetryClient.StartOperation("name", customOperationId)) + { + Assert.IsNotNull(Activity.Current); + Assert.AreNotEqual(activity, Activity.Current.Parent); + Assert.AreEqual(customOperationId, Activity.Current.TraceId.ToHexString()); + Assert.AreEqual(customOperationId, operation.Telemetry.Context.Operation.Id); + Assert.IsNull(operation.Telemetry.Context.Operation.ParentId); + } + + Assert.AreEqual(activity, Activity.Current); + Assert.AreEqual(1, this.sendItems.Count); + Assert.IsTrue(this.sendItems.Single() is DependencyTelemetry); + + var dependency = this.sendItems.Single() as DependencyTelemetry; + + Assert.AreEqual(customOperationId, dependency.Context.Operation.Id); + Assert.IsNull(dependency.Context.Operation.ParentId); + } + + [TestMethod] + public void StartStopRespectsUserProvidedIdsInScopeOfAnotherActivityExplicitIdsW3COff() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + var activity = new Activity("foo").Start(); + + var customOperationId = ActivityTraceId.CreateRandom().ToHexString(); + var customParentId = ActivitySpanId.CreateRandom().ToHexString(); + + using (var operation = this.telemetryClient.StartOperation("name", customOperationId, customParentId)) + { + Assert.IsNotNull(Activity.Current); + Assert.AreNotEqual(activity, Activity.Current.Parent); + Assert.AreEqual(customOperationId, Activity.Current.RootId); + Assert.AreEqual(customOperationId, operation.Telemetry.Context.Operation.Id); + Assert.AreEqual(customParentId, operation.Telemetry.Context.Operation.ParentId); + } + + Assert.AreEqual(activity, Activity.Current); + Assert.AreEqual(1, this.sendItems.Count); + Assert.IsTrue(this.sendItems.Single() is DependencyTelemetry); + + var dependency = this.sendItems.Single() as DependencyTelemetry; + + Assert.AreEqual(customOperationId, dependency.Context.Operation.Id); + Assert.AreEqual(customParentId, dependency.Context.Operation.ParentId); + } + + [TestMethod] + public void StartStopRespectsUserProvidedIdsInScopeOfAnotherActivityTelemetry() + { + var activity = new Activity("foo").Start(); + + var customOperationId = ActivityTraceId.CreateRandom().ToHexString(); + var customParentId = ActivitySpanId.CreateRandom().ToHexString(); + var dependency = new DependencyTelemetry(); + dependency.Context.Operation.Id = customOperationId; + dependency.Context.Operation.ParentId = customParentId; + + using (var operation = this.telemetryClient.StartOperation(dependency)) + { + Assert.IsNotNull(Activity.Current); + Assert.AreNotEqual(activity, Activity.Current.Parent); + Assert.AreEqual(customOperationId, Activity.Current.TraceId.ToHexString()); + Assert.AreEqual(customOperationId, operation.Telemetry.Context.Operation.Id); + Assert.AreEqual(customParentId, operation.Telemetry.Context.Operation.ParentId); + } + + Assert.AreEqual(activity, Activity.Current); + Assert.AreEqual(1, this.sendItems.Count); + Assert.IsTrue(this.sendItems.Single() is DependencyTelemetry); + + Assert.AreEqual(customOperationId, dependency.Context.Operation.Id); + Assert.AreEqual(customParentId, dependency.Context.Operation.ParentId); + } + + [TestMethod] + public void StartStopRespectsUserProvidedIdsInScopeOfAnotherActivityTelemetryInvalidOperationId() + { + var activity = new Activity("foo").Start(); + + var customOperationId = "customOperationId"; + var customParentId = "customParentId"; + var dependency = new DependencyTelemetry(); + dependency.Context.Operation.Id = customOperationId; + dependency.Context.Operation.ParentId = customParentId; + + using (var operation = this.telemetryClient.StartOperation(dependency)) + { + Assert.IsNotNull(Activity.Current); + Assert.AreEqual(activity, Activity.Current.Parent); + Assert.IsTrue(W3CUtilities.IsCompatibleW3CTraceId(Activity.Current.TraceId.ToHexString())); + Assert.AreEqual(customParentId, operation.Telemetry.Context.Operation.ParentId); + } + + Assert.AreEqual(activity, Activity.Current); + Assert.AreEqual(1, this.sendItems.Count); + Assert.IsTrue(this.sendItems.Single() is DependencyTelemetry); + + Assert.AreNotEqual(customOperationId, dependency.Context.Operation.Id); + Assert.AreEqual(customParentId, dependency.Context.Operation.ParentId); + + Assert.IsTrue(dependency.Properties.TryGetValue("ai_legacyRootId", out var actualLegacyRootId)); + Assert.AreEqual(customOperationId, actualLegacyRootId); + } + + [TestMethod] + public void StartStopRespectsUserProvidedIdsInvalidOperationId() + { + var customOperationId = "customOperationId"; + var customParentId = "customParentId"; + var dependency = new DependencyTelemetry(); + dependency.Context.Operation.Id = customOperationId; + dependency.Context.Operation.ParentId = customParentId; + + using (var operation = this.telemetryClient.StartOperation(dependency)) + { + Assert.IsNotNull(Activity.Current); + Assert.IsTrue(W3CUtilities.IsCompatibleW3CTraceId(Activity.Current.TraceId.ToHexString())); + Assert.AreEqual(customParentId, operation.Telemetry.Context.Operation.ParentId); + } + + Assert.AreEqual(1, this.sendItems.Count); + Assert.IsTrue(this.sendItems.Single() is DependencyTelemetry); + + Assert.AreNotEqual(customOperationId, dependency.Context.Operation.Id); + Assert.AreEqual(customParentId, dependency.Context.Operation.ParentId); + + Assert.IsTrue(dependency.Properties.TryGetValue("ai_legacyRootId", out var actualLegacyRootId)); + Assert.AreEqual(customOperationId, actualLegacyRootId); + } + [TestMethod] + public void StartOperationCanOverrideOperationIdNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + + try + { + using (this.telemetryClient.StartOperation("Request", "HOME")) + { + } + + Assert.AreEqual(1, this.sendItems.Count); + + var requestTelemetry = (RequestTelemetry)this.sendItems[0]; + Assert.IsNull(requestTelemetry.Context.Operation.ParentId); + Assert.AreEqual("HOME", requestTelemetry.Context.Operation.Id); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + + [TestMethod] + public void StartOperationOperationIdIsIgnoredIfNotW3cCompatible() { using (this.telemetryClient.StartOperation("Request", "HOME")) { @@ -235,28 +485,298 @@ public void StartOperationCanOverrideOperationId() var requestTelemetry = (RequestTelemetry)this.sendItems[0]; Assert.IsNull(requestTelemetry.Context.Operation.ParentId); - Assert.AreEqual("HOME", requestTelemetry.Context.Operation.Id); + Assert.AreEqual("HOME", requestTelemetry.Properties[W3CConstants.LegacyRootIdProperty]); } [TestMethod] - public void StartOperationCanOverrideRootAndParentOperationId() + public void StartOperationOperationIdIsUsedIfW3cCompatible() { - using (this.telemetryClient.StartOperation("Request", operationId: "ROOT", parentOperationId: "PARENT")) + using (this.telemetryClient.StartOperation("Request", "8ee8641cbdd8dd280d239fa2121c7e4e")) + { + } + + Assert.AreEqual(1, this.sendItems.Count); + + var requestTelemetry = (RequestTelemetry)this.sendItems[0]; + Assert.IsNull(requestTelemetry.Context.Operation.ParentId); + Assert.AreEqual("8ee8641cbdd8dd280d239fa2121c7e4e", requestTelemetry.Context.Operation.Id); + } + + [TestMethod] + public void StartOperationCanOverrideRootAndParentOperationIdNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + try + { + using (this.telemetryClient.StartOperation("Request", operationId: "ROOT", parentOperationId: "PARENT")) + { + this.telemetryClient.TrackTrace("child trace"); + } + + Assert.AreEqual(2, this.sendItems.Count); + + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + Assert.AreEqual("PARENT", requestTelemetry.Context.Operation.ParentId); + Assert.AreEqual("ROOT", requestTelemetry.Context.Operation.Id); + + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + Assert.AreEqual(requestTelemetry.Id, traceTelemetry.Context.Operation.ParentId); + Assert.AreEqual("ROOT", traceTelemetry.Context.Operation.Id); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + + [TestMethod] + public void StartOperationCanOverrideRootAndParentOperationIdNotW3CCompatible() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + + try + { + using (this.telemetryClient.StartOperation("Request", operationId: "ROOT", parentOperationId: "PARENT")) + { + this.telemetryClient.TrackTrace("child trace"); + } + + Assert.AreEqual(2, this.sendItems.Count); + + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + Assert.AreEqual("PARENT", requestTelemetry.Context.Operation.ParentId); + Assert.AreEqual("ROOT", requestTelemetry.Context.Operation.Id); + + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + Assert.AreEqual(requestTelemetry.Id, traceTelemetry.Context.Operation.ParentId); + Assert.AreEqual("ROOT", traceTelemetry.Context.Operation.Id); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + + [TestMethod] + public void StartOperationPopulatesContextCorrectlyW3C() + { + // Act - start an operation, and generate telemetry inside it. + using (this.telemetryClient.StartOperation("Request")) { this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); } - Assert.AreEqual(2, this.sendItems.Count); + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + ValidateRootTelemetry(requestTelemetry); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); + } + + [TestMethod] + public void StartOperationPopulatesContextCorrectlyNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + try + { + // Act - start an operation, and generate telemetry inside it. + using (this.telemetryClient.StartOperation("Request")) + { + this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); + } + + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + ValidateRootTelemetry(requestTelemetry, isW3C: false); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + [TestMethod] + public void StartOperationPopulatesContextCorrectlyWithOverridingNonW3CCompatibleRootIdW3C() + { + // Act - start an operation, supply operation ID which is NOT W3C compatible, and generate a telemetry inside it. + using (this.telemetryClient.StartOperation("Request", operationId: NonW3CCompatibleOperationId)) + { + this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); + } + + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + // The user provided operationid will be ignore as it is not W3C compatible, and it will + // be stored inside custom property. var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); - Assert.AreEqual("PARENT", requestTelemetry.Context.Operation.ParentId); - Assert.AreEqual("ROOT", requestTelemetry.Context.Operation.Id); + ValidateRootTelemetry(requestTelemetry); + // Additional Validations. + Assert.AreNotEqual(NonW3CCompatibleOperationId, requestTelemetry.Context.Operation.Id, "Non compatible operation id supplied by user should be ignored in W3C mode."); + Assert.AreEqual(NonW3CCompatibleOperationId, requestTelemetry.Properties[W3CConstants.LegacyRootIdProperty], "Non compatible operation id supplied by user should be stored in custom property"); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); - Assert.AreEqual(requestTelemetry.Id, traceTelemetry.Context.Operation.ParentId); - Assert.AreEqual("ROOT", traceTelemetry.Context.Operation.Id); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); } + [TestMethod] + public void StartOperationPopulatesContextCorrectlyWithOverridingW3CCompatibleRootIdW3C() + { + // Act - start an operation, supply operation ID which is NOT W3C compatible, and generate a telemetry inside it. + using (this.telemetryClient.StartOperation("Request", operationId: W3CCompatibleOperationId)) + { + this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); + } + + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + // The user provided operationid will be used as it is W3C compatible. + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + ValidateRootTelemetry(requestTelemetry, expectedOperationId:W3CCompatibleOperationId); + Assert.AreEqual(W3CCompatibleOperationId, requestTelemetry.Context.Operation.Id, "W3C compatible operation id supplied by user should be used."); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); + } + + [TestMethod] + [Description("For NonW3C, Validate that any root id supplied by user will be respected.")] + public void StartOperationPopulatesContextCorrectlyWithAnyOverridingRootIdNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + + try + { + // Act - start an operation, supply ANY operation ID, and generate a telemetry inside it. + using (this.telemetryClient.StartOperation("Request", operationId: AnyRootId)) + { + this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); + } + + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + // The user provided operationid will be used as is. + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + ValidateRootTelemetry(requestTelemetry, expectedOperationId: AnyRootId, isW3C: false); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + + [TestMethod] + [Description("For W3C, Validate that any parentid id supplied by user will be respected.")] + public void StartOperationPopulatesContextCorrectlyWithAnyOverridingParentIdW3C() + { + // Act - start an operation, supply ANY parent operation ID, and generate a telemetry inside it. + using (this.telemetryClient.StartOperation("Request", operationId: W3CCompatibleOperationId, parentOperationId: AnyParentId)) + { + this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); + } + + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + // The user provided parent operationid will be used as is. + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + ValidateRootTelemetry(requestTelemetry, expectedOperationParentId: AnyParentId); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); + } + + [TestMethod] + [Description("For Non W3C, Validate that any parentid id supplied by user will be respected.")] + public void StartOperationPopulatesContextCorrectlyWithAnyOverridingParentIdNonW3C() + { + ActivityFormatHelper.DisableW3CFormatInActivity(); + + try + { + // Act - start an operation, supply ANY parent operation ID, and generate a telemetry inside it. + using (this.telemetryClient.StartOperation("Request", operationId: AnyRootId, parentOperationId: AnyParentId)) + { + this.telemetryClient.TrackTrace("child trace"); + this.telemetryClient.TrackEvent("child event"); + } + + Assert.AreEqual(3, this.sendItems.Count); + + // The RequestTelemetry is the root operation here. + // The user provided parent operationid will be used as is. + var requestTelemetry = (RequestTelemetry)this.sendItems.Single(t => t is RequestTelemetry); + ValidateRootTelemetry(requestTelemetry, expectedOperationParentId: AnyParentId, isW3C: false); + + // The generated TraceTelemetry should become the child of the root RequestTelemetry + var traceTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, traceTelemetry); + + + // The generated EventTelemetry should become the child of the root RequestTelemetry + var eventTelemetry = (TraceTelemetry)this.sendItems.Single(t => t is TraceTelemetry); + ValidateChildTelemetry(requestTelemetry, eventTelemetry); + } + finally + { + ActivityFormatHelper.EnableW3CFormatInActivity(); + } + } + // + [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void StartOperationThrowsOnNullOperationTelemetry() @@ -307,6 +827,30 @@ public void StopOperationWhenTelemetryIdDoesNotMatchActivityIdButMatchesLegacyId Assert.AreEqual(1, this.sendItems.Count); } + private void ValidateRootTelemetry(OperationTelemetry operationTelemetry, string expectedOperationId = "", string expectedOperationParentId = null, bool isW3C = true) + { + Assert.AreEqual(expectedOperationParentId, operationTelemetry.Context.Operation.ParentId); + Assert.IsNotNull(operationTelemetry.Context.Operation.Id); + + if (!string.IsNullOrEmpty(expectedOperationId)) + { + Assert.AreEqual(expectedOperationId, operationTelemetry.Context.Operation.Id); + } + + if (isW3C) + { + Assert.IsTrue(W3CUtilities.IsCompatibleW3CTraceId(operationTelemetry.Context.Operation.Id)); + } + Assert.IsNotNull(operationTelemetry.Id); + // ID is shaped like |TraceID.SpanID. + Assert.IsTrue(operationTelemetry.Id.Contains(operationTelemetry.Context.Operation.Id)); + } + + private void ValidateChildTelemetry(OperationTelemetry rootOperationTelemetry, ITelemetry childTelemetry) + { + Assert.AreEqual(rootOperationTelemetry.Id, childTelemetry.Context.Operation.ParentId); + Assert.AreEqual(rootOperationTelemetry.Context.Operation.Id, childTelemetry.Context.Operation.Id, "OperationID should be same for all operations in same context"); + } private string GetOperationName(Activity activity) { return activity.Tags.FirstOrDefault(tag => tag.Key == "OperationName").Value; diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientTest.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientTest.cs index ef510f6c9f..37773a0423 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientTest.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/TelemetryClientTest.cs @@ -1182,10 +1182,10 @@ public void ProactivelySampledOutTelemetryIsNotInitialized() var client = new TelemetryClient(configuration); var telemetry = new RequestTelemetry(); - telemetry.IsSampledOutAtHead = true; + telemetry.ProactiveSamplingDecision = SamplingDecision.SampledOut; client.Track(telemetry); - Assert.IsTrue(telemetry.IsSampledOutAtHead); + Assert.AreEqual(SamplingDecision.SampledOut, telemetry.ProactiveSamplingDecision); Assert.AreEqual(0, initializedTelemetry.Count); Assert.IsNull(telemetry.Context.Internal.SdkVersion); Assert.IsNull(telemetry.Context.Internal.NodeName); diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CActivityExtensionsTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CActivityExtensionsTests.cs deleted file mode 100644 index 2e7f06e44a..0000000000 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CActivityExtensionsTests.cs +++ /dev/null @@ -1,399 +0,0 @@ -using Microsoft.ApplicationInsights.DataContracts; - -namespace Microsoft.ApplicationInsights.W3C -{ - using System.Diagnostics; - using System.Linq; - using Microsoft.ApplicationInsights.Extensibility.W3C; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - [TestClass] - public class W3CActivityExtensionsTests - { - private const string TraceId = "01010101010101010101010101010101"; - private const string ParenSpanId = "0202020202020202"; - - [TestCleanup] - public void Cleanup() - { - while (Activity.Current != null) - { - Activity.Current.Stop(); - } - } - - [TestMethod] - public void SetInvalidTraceParent() - { - var invalidTraceParents = new[] - { - "123", string.Empty, null, "00-00", "00-00-00", "00-00-00-", "-00-00-00", "00-00-00-00-00", - "00-00-00- ", " -00-00-00", "---", "00---", "00-00--", "00--00-", "00---00" - }; - foreach (var traceparent in invalidTraceParents) - { - var a = new Activity("foo"); - a.SetTraceparent(traceparent); - - Assert.IsFalse(a.Tags.Any(t => t.Key == W3CConstants.ParentSpanIdTag), traceparent); - Assert.IsNull(a.GetParentSpanId()); - Assert.IsNull(a.GetTracestate()); - - Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.Single(t => t.Key == W3CConstants.VersionTag).Value, traceparent); - Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.Single(t => t.Key == W3CConstants.SampledTag).Value, traceparent); - - Assert.IsTrue(a.IsW3CActivity(), traceparent); - Assert.AreEqual(32, a.GetTraceId().Length, traceparent); - Assert.AreEqual(16, a.GetSpanId().Length, traceparent); - - Assert.AreEqual($"{W3CConstants.DefaultVersion}-{a.GetTraceId()}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent(), traceparent); - } - } - - [TestMethod] - public void InvalidTraceIdAllTraceparentIsIgnored() - { - var invalidTraceIds = new[] - { - "123", - "000102030405060708090a0b0c0d0f", // 30 chars - "000102030405060708090a0b0c0d0f0", // 31 char - "000102030405060708090a0b0c0d0f0g", // 32 char non-hex - "000102030405060708090a0b0c0d0f0A", // 32 char upper case - "000102030405060708090a0b0c0d0f000" // 33 chars - }; - foreach (var traceId in invalidTraceIds) - { - var a = new Activity("foo"); - - a.SetTraceparent($"00-{traceId}-{ParenSpanId}-00"); - - Assert.IsFalse(a.Tags.Any(t => t.Key == W3CConstants.ParentSpanIdTag), traceId); - Assert.IsNull(a.GetParentSpanId()); - Assert.IsNull(a.GetTracestate()); - - Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.Single(t => t.Key == W3CConstants.VersionTag).Value, traceId); - Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.Single(t => t.Key == W3CConstants.SampledTag).Value, traceId); - - Assert.IsTrue(a.IsW3CActivity(), traceId); - Assert.AreEqual(32, a.GetTraceId().Length, traceId); - Assert.AreEqual(16, a.GetSpanId().Length, traceId); - - Assert.AreEqual($"{W3CConstants.DefaultVersion}-{a.GetTraceId()}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent(), traceId); - } - } - - [TestMethod] - public void InvalidSapnIdAllTraceparentIsIgnored() - { - var invalidSpanIds = new[] - { - "123", - "00010203040506", // 14 chars - "000102030405060", // 15 char - "000102030405060g", // 16 char non-hex - "000102030405060A", // 16 char upper case - "00010203040506070" // 15 chars - }; - foreach (var parentSpanId in invalidSpanIds) - { - var a = new Activity("foo"); - - a.SetTraceparent($"00-{TraceId}-{parentSpanId}-00"); - - Assert.IsFalse(a.Tags.Any(t => t.Key == W3CConstants.ParentSpanIdTag), parentSpanId); - Assert.IsNull(a.GetParentSpanId()); - Assert.IsNull(a.GetTracestate()); - - Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.Single(t => t.Key == W3CConstants.VersionTag).Value, parentSpanId); - Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.Single(t => t.Key == W3CConstants.SampledTag).Value, parentSpanId); - - Assert.IsTrue(a.IsW3CActivity(), parentSpanId); - Assert.AreEqual(32, a.GetTraceId().Length, parentSpanId); - Assert.AreEqual(16, a.GetSpanId().Length, parentSpanId); - - Assert.AreEqual($"{W3CConstants.DefaultVersion}-{a.GetTraceId()}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent(), parentSpanId); - } - } - - [TestMethod] - public void SetValidTraceParent() - { - var a = new Activity("foo"); - a.SetTraceparent($"00-{TraceId}-{ParenSpanId}-00"); - - Assert.IsTrue(a.IsW3CActivity()); - Assert.AreEqual(TraceId, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.TraceIdTag).Value); - Assert.AreEqual(ParenSpanId, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.ParentSpanIdTag).Value); - Assert.IsNotNull(a.Tags.SingleOrDefault(t => t.Key == W3CConstants.SpanIdTag)); - Assert.AreEqual(16, a.Tags.Single(t => t.Key == W3CConstants.SpanIdTag).Value.Length); - Assert.AreEqual(W3CConstants.TraceFlagRecordedAndNotRequested, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.SampledTag).Value); - Assert.AreEqual(W3CConstants.DefaultVersion, a.Tags.SingleOrDefault(t => t.Key == W3CConstants.VersionTag).Value); - - Assert.AreEqual(TraceId, a.GetTraceId()); - Assert.AreEqual(ParenSpanId, a.GetParentSpanId()); - Assert.IsNotNull(a.GetSpanId()); - Assert.AreEqual(a.Tags.Single(t => t.Key == W3CConstants.SpanIdTag).Value, a.GetSpanId()); - Assert.AreEqual($"{W3CConstants.DefaultVersion}-{TraceId}-{a.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndNotRequested}", a.GetTraceparent()); - Assert.IsNull(a.GetTracestate()); - } - - [TestMethod] - public void UpdateContextWithoutParent() - { - var a = new Activity("foo"); - - Assert.IsFalse(a.IsW3CActivity()); - - a.UpdateContextOnActivity(); - Assert.IsTrue(a.IsW3CActivity()); - Assert.IsNotNull(a.GetTraceId()); - Assert.IsNotNull(a.GetSpanId()); - Assert.IsNull(a.GetParentSpanId()); - Assert.IsNotNull(a.GetSpanId()); - - Assert.AreEqual($"00-{a.GetTraceId()}-{a.GetSpanId()}-02", a.GetTraceparent()); - Assert.IsNull(a.GetTracestate()); - } - - [TestMethod] - public void UpdateContextFromCompatibleRootId() - { - var a = new Activity("foo"); - a.SetParentId(TraceId); - - Assert.IsFalse(a.IsW3CActivity()); - - a.UpdateContextOnActivity(); - Assert.IsTrue(a.IsW3CActivity()); - Assert.AreEqual(TraceId, a.GetTraceId()); - Assert.IsNotNull(a.GetSpanId()); - Assert.IsNull(a.GetParentSpanId()); - Assert.IsNotNull(a.GetSpanId()); - - Assert.AreEqual($"00-{a.GetTraceId()}-{a.GetSpanId()}-02", a.GetTraceparent()); - Assert.IsNull(a.GetTracestate()); - } - - [TestMethod] - public void UpdateContextFromIncompatibleRootId() - { - var a = new Activity("foo"); - a.SetParentId("abc"); - - Assert.IsFalse(a.IsW3CActivity()); - - a.UpdateContextOnActivity(); - Assert.IsTrue(a.IsW3CActivity()); - Assert.AreNotEqual("abc", a.GetTraceId()); - Assert.IsNotNull(a.GetTraceId()); - Assert.IsNotNull(a.GetSpanId()); - Assert.IsNull(a.GetParentSpanId()); - Assert.IsNotNull(a.GetSpanId()); - - Assert.AreEqual($"00-{a.GetTraceId()}-{a.GetSpanId()}-02", a.GetTraceparent()); - Assert.IsNull(a.GetTracestate()); - } - - [TestMethod] - public void UpdateContextWithParent() - { - var parent = new Activity("foo").Start(); - parent.SetTraceparent($"00-{TraceId}-{ParenSpanId}-01"); - parent.SetTracestate("some=state"); - var child = new Activity("bar").Start(); - child.UpdateContextOnActivity(); - - Assert.IsTrue(child.IsW3CActivity()); - Assert.AreEqual(TraceId, child.GetTraceId()); - Assert.AreEqual(parent.GetSpanId(), child.GetParentSpanId()); - Assert.AreEqual($"{W3CConstants.DefaultVersion}-{TraceId}-{child.GetSpanId()}-{W3CConstants.TraceFlagRecordedAndRequested}", child.GetTraceparent()); - Assert.AreEqual(parent.GetTracestate(), child.GetTracestate()); - } - - [TestMethod] - public void SetTraceState() - { - var a = new Activity("foo").Start(); - a.SetTracestate("some=state"); - Assert.AreEqual("some=state", a.GetTracestate()); - } - - [TestMethod] - public void UnsupportedVersionsAreIgnored() - { - var a = new Activity("foo").Start(); - a.SetTraceparent($"12-{TraceId}-{ParenSpanId}-00"); - - var b = new Activity("bar").Start(); - b.SetTraceparent($"ff-{TraceId}-{ParenSpanId}-00"); - - Assert.AreEqual($"00-{TraceId}-{a.GetSpanId()}-02", a.GetTraceparent()); - Assert.AreEqual($"00-{TraceId}-{b.GetSpanId()}-02", b.GetTraceparent()); - } - - [TestMethod] - public void RequestedFlagIsRespected() - { - var requestedParents = new[] { "01", "03", "05", "ff" }; - var notRequestedParents = new[] { "00", "02", "04", "fe" }; - - foreach (var req in requestedParents) - { - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{TraceId}-{ParenSpanId}-{req}"); - Assert.AreEqual($"00-{TraceId}-{a.GetSpanId()}-03", a.GetTraceparent(), req); - } - - foreach (var notReq in notRequestedParents) - { - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{TraceId}-{ParenSpanId}-{notReq}"); - Assert.AreEqual($"00-{TraceId}-{a.GetSpanId()}-02", a.GetTraceparent(), notReq); - } - } - - [TestMethod] - public void UpdateValidRequestTelemetryWithForceFalse() - { - var traceId = W3CUtilities.GenerateTraceId(); - var parentSpanId = W3CUtilities.GenerateSpanId(); - var spanId = W3CUtilities.GenerateSpanId(); - - var telemetry = new RequestTelemetry(); - telemetry.Context.Operation.Id = traceId; - telemetry.Context.Operation.ParentId = $"|{traceId}.{parentSpanId}."; - telemetry.Id = $"|{traceId}.{spanId}."; - - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{traceId}-{spanId}-01"); - - a.UpdateTelemetry(telemetry, false); - - Assert.AreEqual(traceId, telemetry.Context.Operation.Id); - -#if NET45 || NET46 - Assert.AreEqual($"|{traceId}.{parentSpanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Id); -#else - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{a.GetSpanId()}.", telemetry.Id); -#endif - } - - [TestMethod] - public void UpdateValidRequestTelemetryWithForceInvalidIdFalse() - { - var traceId = W3CUtilities.GenerateTraceId(); - var parentSpanId = W3CUtilities.GenerateSpanId(); - var spanId = W3CUtilities.GenerateSpanId(); - - var telemetry = new RequestTelemetry(); - telemetry.Context.Operation.Id = traceId; - telemetry.Context.Operation.ParentId = $"|{traceId}.{parentSpanId}."; - telemetry.Id = "|123.456."; - - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{traceId}-{spanId}-01"); - - a.UpdateTelemetry(telemetry, false); - - Assert.AreEqual(traceId, telemetry.Context.Operation.Id); - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{a.GetSpanId()}.", telemetry.Id); - } - - [TestMethod] - public void UpdateValidRequestTelemetryWithForceTrue() - { - var traceId = W3CUtilities.GenerateTraceId(); - var parentSpanId = W3CUtilities.GenerateSpanId(); - var spanId = W3CUtilities.GenerateSpanId(); - - var telemetry = new RequestTelemetry(); - telemetry.Context.Operation.Id = traceId; - telemetry.Context.Operation.ParentId = $"|{traceId}.{parentSpanId}."; - telemetry.Id = $"|{traceId}.{spanId}."; - - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{traceId}-{spanId}-01"); - - a.UpdateTelemetry(telemetry, true); - - Assert.AreEqual(traceId, telemetry.Context.Operation.Id); - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{a.GetSpanId()}.", telemetry.Id); - } - - [TestMethod] - public void UpdateValidDependencyTelemetryWithForceFalse() - { - var traceId = W3CUtilities.GenerateTraceId(); - var parentSpanId = W3CUtilities.GenerateSpanId(); - var spanId = W3CUtilities.GenerateSpanId(); - - var telemetry = new DependencyTelemetry(); - telemetry.Context.Operation.Id = traceId; - telemetry.Context.Operation.ParentId = $"|{traceId}.{parentSpanId}."; - telemetry.Id = $"|{traceId}.{spanId}."; - - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{traceId}-{spanId}-01"); - - a.UpdateTelemetry(telemetry, false); - - Assert.AreEqual(traceId, telemetry.Context.Operation.Id); -#if NET45 || NET46 - Assert.AreEqual($"|{traceId}.{parentSpanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Id); -#else - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{a.GetSpanId()}.", telemetry.Id); -#endif - } - - [TestMethod] - public void UpdateValidDependencyTelemetryWithForceInvalidIdFalse() - { - var traceId = W3CUtilities.GenerateTraceId(); - var parentSpanId = W3CUtilities.GenerateSpanId(); - var spanId = W3CUtilities.GenerateSpanId(); - - var telemetry = new DependencyTelemetry(); - telemetry.Context.Operation.Id = traceId; - telemetry.Context.Operation.ParentId = $"|{traceId}.{parentSpanId}."; - telemetry.Id = "|123.456."; - - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{traceId}-{spanId}-01"); - - a.UpdateTelemetry(telemetry, false); - - Assert.AreEqual(traceId, telemetry.Context.Operation.Id); - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{a.GetSpanId()}.", telemetry.Id); - } - - [TestMethod] - public void UpdateValidDependencyTelemetryTelemetryWithForceTrue() - { - var traceId = W3CUtilities.GenerateTraceId(); - var parentSpanId = W3CUtilities.GenerateSpanId(); - var spanId = W3CUtilities.GenerateSpanId(); - - var telemetry = new DependencyTelemetry(); - telemetry.Context.Operation.Id = traceId; - telemetry.Context.Operation.ParentId = $"|{traceId}.{parentSpanId}."; - telemetry.Id = $"|{traceId}.{spanId}."; - - var a = new Activity("foo").Start(); - a.SetTraceparent($"00-{traceId}-{spanId}-01"); - - a.UpdateTelemetry(telemetry, true); - - Assert.AreEqual(traceId, telemetry.Context.Operation.Id); - Assert.AreEqual($"|{traceId}.{spanId}.", telemetry.Context.Operation.ParentId); - Assert.AreEqual($"|{traceId}.{a.GetSpanId()}.", telemetry.Id); - } - } -} diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3COperationCorrelationTelemetryInitializerTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3COperationCorrelationTelemetryInitializerTests.cs deleted file mode 100644 index 007fab979e..0000000000 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3COperationCorrelationTelemetryInitializerTests.cs +++ /dev/null @@ -1,259 +0,0 @@ -namespace Microsoft.ApplicationInsights.W3C -{ - using System.Diagnostics; - using System.Linq; - using Microsoft.ApplicationInsights.DataContracts; - using Microsoft.ApplicationInsights.Extensibility.Implementation; - using Microsoft.ApplicationInsights.Extensibility.W3C; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - [TestClass] - public class W3COperationCorrelationTelemetryInitializerTests - { - [TestCleanup] - public void Cleanup() - { - while (Activity.Current != null) - { - Activity.Current.Stop(); - } - } - - [TestMethod] - public void InitializerCreatesNewW3CContext() - { - Activity a = new Activity("dummy") - .Start(); - - RequestTelemetry request = new RequestTelemetry(); - - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.IsNotNull(request.Context.Operation.Id); - Assert.IsNull(request.Context.Operation.ParentId); - Assert.AreEqual($"|{a.GetTraceId()}.{a.GetSpanId()}.", request.Id); - - Assert.AreEqual(2, request.Properties.Count); - - Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty)); - Assert.AreEqual(a.Id, request.Properties[W3CConstants.LegacyRequestIdProperty]); - - Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty)); - Assert.AreEqual(a.RootId, request.Properties[W3CConstants.LegacyRootIdProperty]); - } - - [TestMethod] - public void InitializerSetsCorrelationIdsOnTraceTelemetry() - { - Activity a = new Activity("dummy") - .Start() - .GenerateW3CContext(); - - string expectedTrace = a.GetTraceId(); - string expectedParent = a.GetSpanId(); - - TraceTelemetry trace = new TraceTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(trace); - - Assert.AreEqual(expectedTrace, trace.Context.Operation.Id); - Assert.AreEqual($"|{expectedTrace}.{expectedParent}.", trace.Context.Operation.ParentId); - - Assert.IsFalse(trace.Properties.Any()); - } - - [TestMethod] - public void InitializerSetsCorrelationIdsOnRequestTelemetry() - { - Activity a = new Activity("dummy") - .Start() - .GenerateW3CContext(); - - string expectedTrace = a.GetTraceId(); - string expectedSpanId = a.GetSpanId(); - - string expectedParent = "0123456789abcdef"; - a.AddTag(W3CConstants.ParentSpanIdTag, expectedParent); - - RequestTelemetry request = new RequestTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.AreEqual(expectedTrace, request.Context.Operation.Id); - Assert.AreEqual($"|{expectedTrace}.{expectedParent}.", request.Context.Operation.ParentId); - Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id); - - Assert.AreEqual(2, request.Properties.Count); - - Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty)); - Assert.AreEqual(a.Id, request.Properties[W3CConstants.LegacyRequestIdProperty]); - - Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty)); - Assert.AreEqual(a.RootId, request.Properties[W3CConstants.LegacyRootIdProperty]); - } - - [TestMethod] - public void InitializerSetsCorrelationIdsOnRequestTelemetryNoParent() - { - Activity a = new Activity("dummy") - .Start() - .GenerateW3CContext(); - - string expectedTrace = a.GetTraceId(); - string expectedSpanId = a.GetSpanId(); - - RequestTelemetry request = new RequestTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.AreEqual(expectedTrace, request.Context.Operation.Id); - Assert.IsNull(request.Context.Operation.ParentId); - Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id); - - Assert.AreEqual(2, request.Properties.Count); - - Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRequestIdProperty)); - Assert.AreEqual(a.Id, request.Properties[W3CConstants.LegacyRequestIdProperty]); - - Assert.IsTrue(request.Properties.ContainsKey(W3CConstants.LegacyRootIdProperty)); - Assert.AreEqual(a.RootId, request.Properties[W3CConstants.LegacyRootIdProperty]); - } - - [TestMethod] - public void InitializerNoopWithoutActivity() - { - RequestTelemetry request = new RequestTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.IsNull(request.Context.Operation.Id); - Assert.IsNull(request.Context.Operation.ParentId); - - Assert.IsFalse(request.Properties.Any()); - } - - [TestMethod] - public void InitializerIgnoresExistingValues() - { - Activity a = new Activity("dummy") - .Start() - .GenerateW3CContext(); - - string expectedTrace = a.GetTraceId(); - string expectedSpanId = a.GetSpanId(); - - string expectedParent = "0123456789abcdef"; - a.AddTag(W3CConstants.ParentSpanIdTag, expectedParent); - - RequestTelemetry request = new RequestTelemetry(); - - request.Context.Operation.Id = "operation id"; - request.Context.Operation.ParentId = "parent id"; - request.Id = "id"; - - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.AreEqual(expectedTrace, request.Context.Operation.Id); - Assert.AreEqual($"|{expectedTrace}.{expectedParent}.", request.Context.Operation.ParentId); - Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id); - } - - [TestMethod] - public void InitializerPopulatesTraceStateOnRequestAndDependencyTelemetry() - { - Activity a = new Activity("dummy") - .Start() - .GenerateW3CContext(); - - a.SetTracestate("key=value"); - - string expectedTrace = a.GetTraceId(); - string expectedSpanId = a.GetSpanId(); - - RequestTelemetry request = new RequestTelemetry(); - DependencyTelemetry dependency = new DependencyTelemetry(); - TraceTelemetry trace = new TraceTelemetry(); - var initializer = new W3COperationCorrelationTelemetryInitializer(); - initializer.Initialize(request); - initializer.Initialize(dependency); - initializer.Initialize(trace); - - Assert.AreEqual(expectedTrace, request.Context.Operation.Id); - Assert.AreEqual($"|{expectedTrace}.{expectedSpanId}.", request.Id); - - Assert.AreEqual("key=value", request.Properties[W3CConstants.TracestateTag]); - Assert.AreEqual("key=value", dependency.Properties[W3CConstants.TracestateTag]); - Assert.IsFalse(trace.Properties.Any()); - } - - [TestMethod] - public void InitializerOnNestedActivitities() - { - Activity requestActivity = new Activity("request") - .Start(); - - RequestTelemetry request = new RequestTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Activity nested1 = new Activity("nested1").Start(); - Activity nested2 = new Activity("nested1").Start(); - - DependencyTelemetry dependency2 = new DependencyTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(dependency2); - - Assert.AreEqual(request.Context.Operation.Id, nested2.GetTraceId()); - Assert.AreEqual(request.Context.Operation.Id, nested1.GetTraceId()); - - Assert.AreEqual(request.Id, $"|{nested1.GetTraceId()}.{nested1.GetParentSpanId()}."); - Assert.AreEqual(nested1.GetSpanId(), nested2.GetParentSpanId()); - - Assert.AreEqual(request.Context.Operation.Id, dependency2.Context.Operation.Id); - - nested2.Stop(); - - DependencyTelemetry dependency1 = new DependencyTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(dependency1); - - Assert.AreEqual(request.Id, $"|{nested1.GetTraceId()}.{nested1.GetParentSpanId()}."); - Assert.AreEqual(dependency2.Context.Operation.ParentId, dependency1.Id); - Assert.AreEqual(request.Context.Operation.Id, dependency1.Context.Operation.Id); - Assert.AreEqual(request.Id, dependency1.Context.Operation.ParentId); - } - - [TestMethod] - public void InitializerOnSqlDependency() - { - Activity requestActivity = new Activity("request") - .Start() - .GenerateW3CContext(); - - RequestTelemetry request = new RequestTelemetry(); - DependencyTelemetry sqlDependency = new DependencyTelemetry() - { - Type = "SQL" - }; - sqlDependency.Context.GetInternalContext().SdkVersion = "rdddsc:12345"; - string expectedId = sqlDependency.Id; - - new W3COperationCorrelationTelemetryInitializer().Initialize(sqlDependency); - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.AreEqual(request.Context.Operation.Id, sqlDependency.Context.Operation.Id); - Assert.AreEqual(request.Id, sqlDependency.Context.Operation.ParentId); - Assert.AreEqual(expectedId, sqlDependency.Id); - } - - [TestMethod] - public void InitializerOnActivityWithParentWithoutW3CTags() - { - Activity parentActivity = new Activity("parent") - .Start(); - Activity childActivity = new Activity("child") - .Start(); - - RequestTelemetry request = new RequestTelemetry(); - new W3COperationCorrelationTelemetryInitializer().Initialize(request); - - Assert.AreEqual(request.Context.Operation.Id, parentActivity.GetTraceId()); - Assert.AreEqual(request.Context.Operation.Id, childActivity.GetTraceId()); - Assert.AreEqual(request.Id, $"|{childActivity.GetTraceId()}.{childActivity.GetSpanId()}."); - Assert.AreEqual(request.Context.Operation.ParentId, $"|{childActivity.GetTraceId()}.{parentActivity.GetSpanId()}."); - } - } -} diff --git a/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CUtilitiesTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CUtilitiesTests.cs index 80639ae025..804aa481c0 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CUtilitiesTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/W3C/W3CUtilitiesTests.cs @@ -7,16 +7,8 @@ [TestClass] public class W3CActivityUtilitiesTests { - private static readonly Regex TraceIdRegex = new Regex("^[a-f0-9]{32}$", RegexOptions.Compiled); private static readonly Regex SpanIdRegex = new Regex("^[a-f0-9]{16}$", RegexOptions.Compiled); - [TestMethod] - public void GenerateTraceIdGeneratesValidId() - { - var traceId = W3CUtilities.GenerateTraceId(); - Assert.IsTrue(TraceIdRegex.IsMatch(traceId)); - } - [TestMethod] public void GenerateSpanIdGeneratesValidId() { diff --git a/Test/Microsoft.ApplicationInsights.Test/Standalone/AppInsightsStandaloneTests.cs b/Test/Microsoft.ApplicationInsights.Test/Standalone/AppInsightsStandaloneTests.cs index d9f04cbb5c..d9c74dd25c 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Standalone/AppInsightsStandaloneTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Standalone/AppInsightsStandaloneTests.cs @@ -7,6 +7,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.DataContracts; [TestClass] public class AppInsightsStandaloneTests @@ -31,24 +33,60 @@ public void Cleanup() [TestMethod] public void AppInsightsDllCouldRunStandalone() { - var dependencyId = RunTestApplication(false, "guid"); + // This tests if ApplicationInsights.dll can work standalone without System.DiagnosticSource (for uses like in a powershell script) + // Its hard to mock this with plain unit tests, so we spin up a dummy application, copy just ApplicationInsights.dll + // and run it. + var dependencyId = RunTestApplication("guid"); Assert.IsFalse(dependencyId.Contains("guid")); } [TestMethod] - public void AppInsightsUsesActivityWhenDiagnosticSourceIsAvailable() + public void AppInsightsUsesActivityWhenDiagnosticSourceIsAvailableNonW3C() { - var dependencyId = RunTestApplication(true, "guid"); - Assert.IsTrue(dependencyId.StartsWith("|guid.")); - } + try + { + // Regular use case - System.DiagnosticSource is available. Regular unit test can cover this scenario. + var config = new TelemetryConfiguration(); + DisableW3CFormatInActivity(); + config.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer()); + var tc = new TelemetryClient(config); + using (var requestOperation = tc.StartOperation("request", "guid")) + { + using (var dependencyOperation = tc.StartOperation("dependency", "guid")) + { + Assert.IsTrue(dependencyOperation.Telemetry.Id.StartsWith("|guid.")); + tc.TrackTrace("Hello World!"); + } + } + } + finally + { + EnableW3CFormatInActivity(); + } + } - private string RunTestApplication(bool withDiagnosticSource, string operationId) + [TestMethod] + public void AppInsightsUsesActivityWhenDiagnosticSourceIsAvailableW3C() { - if (withDiagnosticSource) + // Regular use case - System.DiagnosticSource is available. Regular unit test can cover this scenario. + var config = new TelemetryConfiguration(); + config.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer()); + var tc = new TelemetryClient(config); + using (var requestOperation = tc.StartOperation("request", "guid")) { - File.Copy("System.Diagnostics.DiagnosticSource.dll", $"{this.tempPath}\\System.Diagnostics.DiagnosticSource.dll"); + using (var dependencyOperation = tc.StartOperation("dependency", "guid")) + { + // "guid" is not w3c compatible. Ignored + Assert.IsFalse(dependencyOperation.Telemetry.Id.StartsWith("|guid.")); + // but "guid" will be stored in custom properties + Assert.AreEqual("guid",dependencyOperation.Telemetry.Properties["ai_legacyRootId"]); + tc.TrackTrace("Hello World!"); + } } + } + private string RunTestApplication(string operationId) + { var fileName = $"{this.tempPath}\\ActivityTest.exe"; Assert.IsTrue(CreateTestApplication(fileName)); @@ -68,11 +106,26 @@ private string RunTestApplication(bool withDiagnosticSource, string operationId) p.Start(); Assert.IsTrue(p.WaitForExit(10000)); + Trace.WriteLine(p.StandardOutput.ReadToEnd()); + Trace.WriteLine(p.StandardError.ReadToEnd()); Assert.AreEqual(0, p.ExitCode); + return p.StandardOutput.ReadToEnd(); } + private static void DisableW3CFormatInActivity() + { + Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical; + Activity.ForceDefaultIdFormat = true; + } + + private static void EnableW3CFormatInActivity() + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + } + private static bool CreateTestApplication(string fileName) { SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@" diff --git a/Test/Microsoft.ApplicationInsights.Test/Standalone/Microsoft.ApplicationInsights.Isolated.Tests.csproj b/Test/Microsoft.ApplicationInsights.Test/Standalone/Microsoft.ApplicationInsights.Isolated.Tests.csproj index 62b5059c49..d179673a6a 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Standalone/Microsoft.ApplicationInsights.Isolated.Tests.csproj +++ b/Test/Microsoft.ApplicationInsights.Test/Standalone/Microsoft.ApplicationInsights.Isolated.Tests.csproj @@ -36,7 +36,7 @@ - + diff --git a/Test/Microsoft.ApplicationInsights.Test/netcoreapp11/Microsoft.ApplicationInsights.netcoreapp11.Tests.csproj b/Test/Microsoft.ApplicationInsights.Test/netcoreapp11/Microsoft.ApplicationInsights.netcoreapp11.Tests.csproj index 6003758f12..ad0ecbd6d8 100644 --- a/Test/Microsoft.ApplicationInsights.Test/netcoreapp11/Microsoft.ApplicationInsights.netcoreapp11.Tests.csproj +++ b/Test/Microsoft.ApplicationInsights.Test/netcoreapp11/Microsoft.ApplicationInsights.netcoreapp11.Tests.csproj @@ -25,7 +25,7 @@ - + diff --git a/Test/Microsoft.ApplicationInsights.Test/netcoreapp20/Microsoft.ApplicationInsights.netcoreapp20.Tests.csproj b/Test/Microsoft.ApplicationInsights.Test/netcoreapp20/Microsoft.ApplicationInsights.netcoreapp20.Tests.csproj index c343c3b69d..2f349ef011 100644 --- a/Test/Microsoft.ApplicationInsights.Test/netcoreapp20/Microsoft.ApplicationInsights.netcoreapp20.Tests.csproj +++ b/Test/Microsoft.ApplicationInsights.Test/netcoreapp20/Microsoft.ApplicationInsights.netcoreapp20.Tests.csproj @@ -26,7 +26,7 @@ - + diff --git a/Test/ServerTelemetryChannel.Test/Net45.Tests/TelemetryChannel.Net45.Tests.csproj b/Test/ServerTelemetryChannel.Test/Net45.Tests/TelemetryChannel.Net45.Tests.csproj index 569313f1ed..08b0f55727 100644 --- a/Test/ServerTelemetryChannel.Test/Net45.Tests/TelemetryChannel.Net45.Tests.csproj +++ b/Test/ServerTelemetryChannel.Test/Net45.Tests/TelemetryChannel.Net45.Tests.csproj @@ -31,7 +31,7 @@ - + {3273d899-d9b3-44fe-b3ab-578e18b2ef90} TelemetryChannel diff --git a/Test/ServerTelemetryChannel.Test/NetCore.Tests/TelemetryChannel.netcoreapp11.Tests.csproj b/Test/ServerTelemetryChannel.Test/NetCore.Tests/TelemetryChannel.netcoreapp11.Tests.csproj index 8dfea996f8..f5da850380 100644 --- a/Test/ServerTelemetryChannel.Test/NetCore.Tests/TelemetryChannel.netcoreapp11.Tests.csproj +++ b/Test/ServerTelemetryChannel.Test/NetCore.Tests/TelemetryChannel.netcoreapp11.Tests.csproj @@ -34,7 +34,7 @@ - + diff --git a/Test/ServerTelemetryChannel.Test/NetCore20.Tests/TelemetryChannel.netcoreapp20.Tests.csproj b/Test/ServerTelemetryChannel.Test/NetCore20.Tests/TelemetryChannel.netcoreapp20.Tests.csproj index c522b8f577..8ac402a7fe 100644 --- a/Test/ServerTelemetryChannel.Test/NetCore20.Tests/TelemetryChannel.netcoreapp20.Tests.csproj +++ b/Test/ServerTelemetryChannel.Test/NetCore20.Tests/TelemetryChannel.netcoreapp20.Tests.csproj @@ -31,7 +31,7 @@ - + diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/AdaptiveSamplingTelemetryProcessorTest.cs b/Test/ServerTelemetryChannel.Test/Shared.Tests/AdaptiveSamplingTelemetryProcessorTest.cs index 1769a9baf5..850909d509 100644 --- a/Test/ServerTelemetryChannel.Test/Shared.Tests/AdaptiveSamplingTelemetryProcessorTest.cs +++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/AdaptiveSamplingTelemetryProcessorTest.cs @@ -64,6 +64,156 @@ public void AllTelemetryCapturedWhenProductionRateIsLow() Assert.AreEqual(itemsProduced, sentTelemetry.Count); } +#if !NETCOREAPP1_1 +// Sampling tests are not stable on linux Azure pipelines agent on .NET Core 1.1. +// considering .NET Core 1.1 is no longer supported, let's not run sampling tests there at all + [TestMethod] + public void ProactivelySampledInTelemetryCapturedWhenProactiveSamplingRateIsLowerThanTarget() + { + var testDurationSec = 30; + var proactivelySampledInRatePerSec = 25; + var targetProactiveCount = proactivelySampledInRatePerSec * testDurationSec; + var precision = 0.2; + var (proactivelySampledInAndSentCount, sentCount) = ProactiveSamplingTest( + proactivelySampledInRatePerSec: proactivelySampledInRatePerSec, + beforeSamplingRatePerSec: proactivelySampledInRatePerSec * 3, + targetAfterSamplingRatePerSec: proactivelySampledInRatePerSec * 2, + precision: precision, + testDurationInSec: testDurationSec); // plus warm up + + Trace.WriteLine($"'Ideal' proactively sampled in telemetry item count: {targetProactiveCount}"); + Trace.WriteLine($"Expected range: from {targetProactiveCount - precision * targetProactiveCount} to {targetProactiveCount + precision * targetProactiveCount}"); + Trace.WriteLine( + $"Actual proactively sampled in telemetry item count: {proactivelySampledInAndSentCount} ({100.0 * proactivelySampledInAndSentCount / targetProactiveCount:##.##}% of ideal)"); + + // all proactively sampled in should be sent assuming we have perfect algo + // as they happen with rate 5 items per sec and we want 10 rate of sent telemetry + Assert.IsTrue(proactivelySampledInAndSentCount / (double)targetProactiveCount > 1 - precision, + $"Expected {proactivelySampledInAndSentCount} to be between {targetProactiveCount} +/- {targetProactiveCount * precision}"); + Assert.IsTrue(proactivelySampledInAndSentCount / (double)targetProactiveCount < 1 + precision, + $"Expected {proactivelySampledInAndSentCount} to be between {targetProactiveCount} +/- {targetProactiveCount * precision}"); + } + + [TestMethod] + public void ProactivelySampledInTelemetryCapturedWhenProactiveSamplingRateIsHigherThanTarget() + { + var testDuration = 30; + var beforeSamplingRate = 42; + var proactiveRate = beforeSamplingRate - 2; + var precision = 0.3; + var (proactivelySampledInAndSentCount, sentCount) = ProactiveSamplingTest( + proactivelySampledInRatePerSec: proactiveRate, + beforeSamplingRatePerSec: beforeSamplingRate, + targetAfterSamplingRatePerSec: proactiveRate / 2, + precision: precision, + testDurationInSec: testDuration); //plus warm up + + // most of of sent should be proactively sampled in + // as proactive happen with rate >> than target + Trace.WriteLine($"'Ideal' proactively sampled in telemetry item count: {sentCount}"); + Trace.WriteLine( + $"Expected range: from {sentCount - sentCount * precision} to {sentCount + sentCount * precision}"); + Trace.WriteLine( + $"Actual proactively sampled in telemetry item count: {proactivelySampledInAndSentCount} ({100.0 * proactivelySampledInAndSentCount / sentCount:##.##}% of ideal)"); + Assert.AreEqual(proactivelySampledInAndSentCount, sentCount, sentCount * precision); + } + + public (int proactivelySampledInAndSentCount, double sentCount) ProactiveSamplingTest( + int proactivelySampledInRatePerSec, + int beforeSamplingRatePerSec, + double targetAfterSamplingRatePerSec, + double precision, + int testDurationInSec) + { + // we'll ignore telemetry reported during first few percentage evaluations + int warmUpInSec = 12; + + // we'll produce proactively sampled in items and also 'normal' items with the same rate + // but allow only proactively sampled in + a bit more + + // number of items produced should be close to target + int targetItemCount = (int)(testDurationInSec * targetAfterSamplingRatePerSec); + + var sentTelemetry = new List(); + + using (var tc = new TelemetryConfiguration() { TelemetryChannel = new StubTelemetryChannel() }) + { + var chainBuilder = new TelemetryProcessorChainBuilder(tc); + + // set up adaptive sampling that evaluates and changes sampling % frequently + chainBuilder + .UseAdaptiveSampling( + new Channel.Implementation.SamplingPercentageEstimatorSettings() + { + // help algo get to stabilize earlier + InitialSamplingPercentage = targetAfterSamplingRatePerSec / (double)beforeSamplingRatePerSec * 100, + MaxTelemetryItemsPerSecond = targetAfterSamplingRatePerSec, + EvaluationInterval = TimeSpan.FromSeconds(2), + SamplingPercentageDecreaseTimeout = TimeSpan.FromSeconds(4), + SamplingPercentageIncreaseTimeout = TimeSpan.FromSeconds(4), + }, + this.TraceSamplingPercentageEvaluation) + .Use((next) => new StubTelemetryProcessor(next) { OnProcess = (t) => sentTelemetry.Add(t) }); + + chainBuilder.Build(); + + var sw = Stopwatch.StartNew(); + var productionTimer = new Timer( + (state) => + { + var requests = new RequestTelemetry[beforeSamplingRatePerSec]; + + for (int i = 0; i < beforeSamplingRatePerSec; i++) + { + requests[i] = new RequestTelemetry() + { + ProactiveSamplingDecision = i < proactivelySampledInRatePerSec ? SamplingDecision.SampledIn : SamplingDecision.None + }; + + requests[i].Context.Operation.Id = ActivityTraceId.CreateRandom().ToHexString(); + } + + foreach (var request in requests) + { + if (((Stopwatch) state).Elapsed.TotalSeconds < warmUpInSec) + { + // let's ignore telemetry from first few rate evaluations - it does not make sense + request.Properties["ignore"] = "true"; + } + + tc.TelemetryProcessorChain.Process(request); + } + }, + sw, + 0, + 1000); + + Thread.Sleep(TimeSpan.FromSeconds(testDurationInSec + warmUpInSec)); + + // dispose timer and wait for callbacks to complete + DisposeTimer(productionTimer); + } + + var notIgnoredSent = sentTelemetry.Where(i => i is ISupportProperties propItem && !propItem.Properties.ContainsKey("ignore")).ToArray(); + + var proactivelySampledInAndSentCount = notIgnoredSent.Count(i => + i is ISupportAdvancedSampling advSamplingItem && + advSamplingItem.ProactiveSamplingDecision == SamplingDecision.SampledIn); + + // check that normal sampling requirements still apply (we generated as much items as expected) + Trace.WriteLine($"'Ideal' telemetry item count: {targetItemCount}"); + Trace.WriteLine($"Expected range: from {targetItemCount - precision * targetItemCount} to {targetItemCount + precision * targetItemCount}"); + Trace.WriteLine( + $"Actual telemetry item count: {notIgnoredSent.Length} ({100.0 * notIgnoredSent.Length / targetItemCount:##.##}% of ideal)"); + Trace.WriteLine( + $"Actual proactive sampled in and sent: {proactivelySampledInAndSentCount}"); + + Assert.IsTrue(notIgnoredSent.Length / (double)targetItemCount > 1 - precision); + Assert.IsTrue(notIgnoredSent.Length / (double)targetItemCount < 1 + precision); + + return (proactivelySampledInAndSentCount, notIgnoredSent.Length); + } + [TestMethod] public void SamplingPercentageAdjustsAccordingToConstantHighProductionRate() { @@ -104,7 +254,7 @@ public void SamplingPercentageAdjustsAccordingToConstantHighProductionRate() productionFrequencyMs); Thread.Sleep(25000); - + // dispose timer and wait for callbacks to complete DisposeTimer(productionTimer); } @@ -121,7 +271,7 @@ public void SamplingPercentageAdjustsAccordingToConstantHighProductionRate() targetItemCount - tolerance, targetItemCount + tolerance)); Trace.WriteLine(string.Format( - "Actual telemetry item count: {0} ({1:##.##}% of ideal)", + "Actual telemetry item count: {0} ({1:##.##}% of ideal)", sentTelemetry.Count, 100.0 * sentTelemetry.Count / targetItemCount)); @@ -207,6 +357,7 @@ public void SamplingPercentageAdjustsForSpikyProductionRate() Assert.IsTrue(sentTelemetry.Count > targetItemCount - tolerance); Assert.IsTrue(sentTelemetry.Count < targetItemCount + tolerance); } +#endif private class AdaptiveTesterMessageSink : ITelemetryProcessor { diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/NetworkAvailabilityTransmissionPolicyTest.cs b/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/NetworkAvailabilityTransmissionPolicyTest.cs index fdbe5dc43c..30c73d8f40 100644 --- a/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/NetworkAvailabilityTransmissionPolicyTest.cs +++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/NetworkAvailabilityTransmissionPolicyTest.cs @@ -58,6 +58,25 @@ public void SetsMaxSenderAndBufferCapacitiesToZeroWhenNetworkIsUnavailable() Assert.AreEqual(0, policy.MaxBufferCapacity); } + [TestMethod] + public void InitializeCatchAllExceptionsAndDoesNotSetCapacity() + { + var network = new StubNetwork { OnIsAvailable = () => { throw new Exception("error"); } }; + var policy = new NetworkAvailabilityTransmissionPolicy(network); + + try + { + policy.Initialize(new StubTransmitter()); + } + catch(Exception ex) + { + Assert.Fail("No exception should have been thrown from Initialize. Exception thrown: " + ex.ToString()); + } + + Assert.IsNull(policy.MaxSenderCapacity); + Assert.IsNull(policy.MaxBufferCapacity); + } + [TestMethod] public void DoesNotSetMaxSenderAndBufferCapacitiesToZeroWhenNetworkIsAvailable() { diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/SamplingInternals/SamplingIncludesUtilityTests.cs b/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/SamplingInternals/SamplingIncludesUtilityTests.cs new file mode 100644 index 0000000000..f4801ea171 --- /dev/null +++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/SamplingInternals/SamplingIncludesUtilityTests.cs @@ -0,0 +1,70 @@ +namespace Microsoft.ApplicationInsights.WindowsServer.Channel.Implementation.SamplingInternals +{ + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.Implementation.SamplingInternals; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SamplingIncludesUtilityTests + { + [TestMethod] + public void VerifyIncludes() + { + string input = "DEPENDENCY;EVENT"; + + var test = SamplingIncludesUtility.CalculateFromIncludes(input); + + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.RemoteDependency)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Event)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Exception)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.PageView)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Request)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Message)); + } + + [TestMethod] + public void VerifyExcludes() + { + string input = "DEPENDENCY;EVENT"; + + var test = SamplingIncludesUtility.CalculateFromExcludes(input); + + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.RemoteDependency)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Event)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Exception)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.PageView)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Request)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Message)); + } + + [TestMethod] + public void VerifyIncludesBehaviorWhenBadConfig() + { + string input = "car;truck;train;"; + + var test = SamplingIncludesUtility.CalculateFromIncludes(input); + + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.RemoteDependency)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Event)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Exception)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.PageView)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Request)); + Assert.IsFalse(test.HasFlag(SamplingTelemetryItemTypes.Message)); + } + + [TestMethod] + public void VerifyExcludesBehaviorWhenBadConfig() + { + string input = "car;truck;train;"; + + var test = SamplingIncludesUtility.CalculateFromExcludes(input); + + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.RemoteDependency)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Event)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Exception)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.PageView)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Request)); + Assert.IsTrue(test.HasFlag(SamplingTelemetryItemTypes.Message)); + } + } +} diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs b/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs index 3bbf17db22..93bdf78e3b 100644 --- a/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs +++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Diagnostics; using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Extensibility; @@ -15,10 +16,10 @@ [TestClass] public class SamplingTelemetryProcessorTest { - Random random = new Random(); + readonly Random random = new Random(); [TestMethod] - public void ThrowsAgrumentNullExceptionWithoutNextPocessor() + public void ThrowsArgumentNullExceptionWithoutNextProcessor() { AssertEx.Throws(() => new SamplingTelemetryProcessor(null)); } @@ -28,7 +29,8 @@ public void DefaultSamplingRateIs100Percent() { var processor = new SamplingTelemetryProcessor(new StubTelemetryProcessor(null)); - Assert.AreEqual(processor.SamplingPercentage, 100.0, 12); + Assert.AreEqual(processor.SamplingPercentage, 100.0); + Assert.IsNull(processor.ProactiveSamplingPercentage); } [TestMethod] @@ -394,7 +396,7 @@ public void ProactivelySampledOutItemIsNotSent() sentTelemetry, 100); var sampledOutTelemetry = new RequestTelemetry(); - sampledOutTelemetry.IsSampledOutAtHead = true; + sampledOutTelemetry.ProactiveSamplingDecision = SamplingDecision.SampledOut; telemetryProcessorChainWithSampling.Process(sampledOutTelemetry); telemetryProcessorChainWithSampling.Dispose(); @@ -412,7 +414,7 @@ public void ProactivelySampledOutItemIsNotSentEvenIfItIsSampledIn() for (int i = 0; i < 100; i++) { var sampledOutTelemetry = new RequestTelemetry(); - sampledOutTelemetry.IsSampledOutAtHead = true; + sampledOutTelemetry.ProactiveSamplingDecision = SamplingDecision.SampledOut; telemetryProcessorChainWithSampling.Process(sampledOutTelemetry); } @@ -438,7 +440,7 @@ public void ProactivelySampledOutItemThatIsLaterSampledInIsAddedToTheNextSampled var sampledOutTelemetry = new RequestTelemetry(); // This makes those items proactively sampled out - sampledOutTelemetry.IsSampledOutAtHead = true; + sampledOutTelemetry.ProactiveSamplingDecision = SamplingDecision.SampledOut; // This operation ID hash is lower than 25, so every item in this batch is sampled in sampledOutTelemetry.Context.Operation.Id = "abcdfeghijk"; @@ -469,6 +471,111 @@ public void ProactivelySampledOutItemThatIsLaterSampledInIsAddedToTheNextSampled sentTelemetry.ForEach((item) => Assert.AreEqual(50, ((ISupportSampling)item).SamplingPercentage)); } + [TestMethod] + public void ProactivelySampledInItemsAreNotGivenPriorityIfRatesAreNotSet() + { + var sentTelemetry = new List(); + TelemetryProcessorChain telemetryProcessorChainWithSampling = CreateTelemetryProcessorChainWithSampling( + sentTelemetry, + 50); + + for (int i = 0; i < 1000; i++) + { + var item = new RequestTelemetry(); + item.Context.Operation.Id = ActivityTraceId.CreateRandom().ToHexString(); + + // proactively sample in items with big score, so they should not be sampled in + if (SamplingScoreGenerator.GetSamplingScore(item.Context.Operation.Id) > 50) + { + item.ProactiveSamplingDecision = SamplingDecision.SampledIn; + } + + telemetryProcessorChainWithSampling.Process(item); + } + + Assert.AreEqual(0, sentTelemetry.Count(i => ((ISupportAdvancedSampling)i).ProactiveSamplingDecision == SamplingDecision.SampledIn)); + } + + [TestMethod] + public void ProactivelySampledInItemsPassIfCurrentRateIsLowerThanExpected() + { + var sentTelemetry = new List(); + + var tc = new TelemetryConfiguration + { + TelemetryChannel = new StubTelemetryChannel(), + InstrumentationKey = Guid.NewGuid().ToString("D") + }; + + var channelBuilder = new TelemetryProcessorChainBuilder(tc); + channelBuilder.Use(next => new SamplingTelemetryProcessor(next) + { + SamplingPercentage = 50, + ProactiveSamplingPercentage = 100 + }); + channelBuilder.Use(next => new StubTelemetryProcessor(next) { OnProcess = t => sentTelemetry.Add(t) }); + channelBuilder.Build(); + + int sampledInCount = 0; + for (int i = 0; i < 1000; i++) + { + var item = new RequestTelemetry(); + item.Context.Operation.Id = ActivityTraceId.CreateRandom().ToHexString(); + + // sample in random items - they all should pass through regardless of the score + if (i % 2 == 0) + { + item.ProactiveSamplingDecision = SamplingDecision.SampledIn; + sampledInCount++; + } + + tc.TelemetryProcessorChain.Process(item); + } + + // all proactively sampled in items passed through regardless of their score. + Assert.AreEqual(sampledInCount, sentTelemetry.Count(i => ((ISupportAdvancedSampling)i).ProactiveSamplingDecision == SamplingDecision.SampledIn)); + } + + [TestMethod] + public void ProactivelySampledInItemsPassAccordingToScoreIfCurrentRateIsHigherThanExpected() + { + var sentTelemetry = new List(); + + var tc = new TelemetryConfiguration + { + TelemetryChannel = new StubTelemetryChannel(), + InstrumentationKey = Guid.NewGuid().ToString("D") + }; + + var channelBuilder = new TelemetryProcessorChainBuilder(tc); + channelBuilder.Use(next => new SamplingTelemetryProcessor(next) + { + SamplingPercentage = 50, + ProactiveSamplingPercentage = 50 + }); + channelBuilder.Use(next => new StubTelemetryProcessor(next) { OnProcess = t => sentTelemetry.Add(t) }); + channelBuilder.Build(); + + int count = 5000; + for (int i = 0; i < count; i++) + { + var item = new RequestTelemetry(); + item.Context.Operation.Id = ActivityTraceId.CreateRandom().ToHexString(); + + // generate a lot sampled-in items, only 1/CurrentProactiveSampledInRatioToTarget of them should pass through + // and SamplingPercentage of sampled-out items + if (SamplingScoreGenerator.GetSamplingScore(item.Context.Operation.Id) < 80) + { + item.ProactiveSamplingDecision = SamplingDecision.SampledIn; + } + + tc.TelemetryProcessorChain.Process(item); + } + + Assert.AreEqual(0, sentTelemetry.Count(i => ((ISupportAdvancedSampling)i).ProactiveSamplingDecision == SamplingDecision.None)); + Assert.AreEqual(count / 2, sentTelemetry.Count(i => ((ISupportAdvancedSampling)i).ProactiveSamplingDecision == SamplingDecision.SampledIn), count / 2 / 10); + } + private static void TelemetryTypeDoesNotSupportSampling(Func sendAction, string excludedTypes = null, string includedTypes = null) { const int SamplingPercentage = 10; @@ -531,8 +638,10 @@ private static void TelemetryTypeDoesNotSupportSampling(Func sentTelemetry, double samplingPercentage, string excludedTypes = null, string includedTypes = null) { - var tc = new TelemetryConfiguration {TelemetryChannel = new StubTelemetryChannel()}; - tc.InstrumentationKey = Guid.NewGuid().ToString("D"); + var tc = new TelemetryConfiguration + { + TelemetryChannel = new StubTelemetryChannel(), InstrumentationKey = Guid.NewGuid().ToString("D") + }; var channelBuilder = new TelemetryProcessorChainBuilder(tc); channelBuilder.UseSampling(samplingPercentage, excludedTypes, includedTypes); @@ -544,8 +653,7 @@ private static TelemetryProcessorChain CreateTelemetryProcessorChainWithSampling foreach (ITelemetryProcessor processor in processors.TelemetryProcessors) { - ITelemetryModule m = processor as ITelemetryModule; - if (m != null) + if (processor is ITelemetryModule m) { m.Initialize(tc); } diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/Shared.Tests.projitems b/Test/ServerTelemetryChannel.Test/Shared.Tests/Shared.Tests.projitems index 5c6132b7c9..f3b6073ab9 100644 --- a/Test/ServerTelemetryChannel.Test/Shared.Tests/Shared.Tests.projitems +++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/Shared.Tests.projitems @@ -40,6 +40,7 @@ + diff --git a/Test/ServerTelemetryChannel.Test/TelemetryChannel.Nuget.Tests/TelemetryChannel.Nuget.Tests.csproj b/Test/ServerTelemetryChannel.Test/TelemetryChannel.Nuget.Tests/TelemetryChannel.Nuget.Tests.csproj index 4254cd3ab8..97fca2b1c2 100644 --- a/Test/ServerTelemetryChannel.Test/TelemetryChannel.Nuget.Tests/TelemetryChannel.Nuget.Tests.csproj +++ b/Test/ServerTelemetryChannel.Test/TelemetryChannel.Nuget.Tests/TelemetryChannel.Nuget.Tests.csproj @@ -31,7 +31,7 @@ - + diff --git a/Test/TestFramework/Shared/ActivityFormatHelper.cs b/Test/TestFramework/Shared/ActivityFormatHelper.cs new file mode 100644 index 0000000000..42124aab51 --- /dev/null +++ b/Test/TestFramework/Shared/ActivityFormatHelper.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Microsoft.ApplicationInsights.TestFramework +{ + internal class ActivityFormatHelper + { + public static void DisableW3CFormatInActivity() + { + Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical; + Activity.ForceDefaultIdFormat = true; + } + + public static void EnableW3CFormatInActivity() + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + } + } +} diff --git a/Test/TestFramework/Shared/ExpectedExceptionWithMessageAttribute.cs b/Test/TestFramework/Shared/ExpectedExceptionWithMessageAttribute.cs new file mode 100644 index 0000000000..3bfd95facf --- /dev/null +++ b/Test/TestFramework/Shared/ExpectedExceptionWithMessageAttribute.cs @@ -0,0 +1,42 @@ +namespace Microsoft.ApplicationInsights.TestFramework +{ + using System; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Extension of class to validate a test based on both the exception type and message. + /// + /// + /// Inspired by StackOverflow answer: https://stackoverflow.com/a/16945443/1466768 + /// + public class ExpectedExceptionWithMessageAttribute : ExpectedExceptionBaseAttribute + { + private readonly Type exceptionType; + private readonly string expectedMessage; + + public ExpectedExceptionWithMessageAttribute(Type exceptionType) : this(exceptionType, null) + { + } + + public ExpectedExceptionWithMessageAttribute(Type exceptionType, string expectedMessage) + { + this.exceptionType = exceptionType; + this.expectedMessage = expectedMessage; + } + + protected override void Verify(Exception ex) + { + if (ex.GetType() != this.exceptionType) + { + Assert.Fail($"Test method threw exception {this.exceptionType.FullName}, but exception {ex.GetType().FullName} was expected. Exception message: {ex.Message}"); + } + + if (this.expectedMessage != null && this.expectedMessage != ex.Message) + { + Assert.Fail($"Test method threw the expected exception type, but with an unexpected message: {ex.Message}"); + } + + Console.Write("ExpectedExceptionWithMessageAttribute:" + ex.Message); + } + } +} diff --git a/Test/TestFramework/Shared/StubPlatform.cs b/Test/TestFramework/Shared/StubPlatform.cs index 3fcdd24df7..2d988ec8bc 100644 --- a/Test/TestFramework/Shared/StubPlatform.cs +++ b/Test/TestFramework/Shared/StubPlatform.cs @@ -1,11 +1,9 @@ namespace Microsoft.ApplicationInsights.TestFramework { using System; - using System.Collections.Generic; - using Microsoft.ApplicationInsights.Channel; + using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; - using Microsoft.ApplicationInsights.Extensibility.Implementation.External; internal class StubPlatform : IPlatform { @@ -23,9 +21,10 @@ public IDebugOutput GetDebugOutput() return this.OnGetDebugOutput(); } - public string GetEnvironmentVariable(string name) + public bool TryGetEnvironmentVariable(string name, out string value) { - return Environment.GetEnvironmentVariable(name); + value = Environment.GetEnvironmentVariable(name); + return !string.IsNullOrEmpty(value); } public string GetMachineName() diff --git a/Test/TestFramework/Shared/StubTelemetryChannel.cs b/Test/TestFramework/Shared/StubTelemetryChannel.cs index 4215277a6a..19d951de43 100644 --- a/Test/TestFramework/Shared/StubTelemetryChannel.cs +++ b/Test/TestFramework/Shared/StubTelemetryChannel.cs @@ -33,6 +33,11 @@ public StubTelemetryChannel() /// Gets or sets a value indicating whether to throw an error. /// public bool ThrowError { get; set; } + + /// + /// Gets or sets an integer value. This field exists to test config parsing. + /// + public int IntegerProperty { get; set; } /// /// Gets or sets the callback invoked by the method. diff --git a/Test/TestFramework/Shared/TestFramework.Shared.projitems b/Test/TestFramework/Shared/TestFramework.Shared.projitems index 830a781b2b..60a8b5a859 100644 --- a/Test/TestFramework/Shared/TestFramework.Shared.projitems +++ b/Test/TestFramework/Shared/TestFramework.Shared.projitems @@ -10,10 +10,12 @@ Microsoft.ApplicationInsights.TestFramework + + diff --git a/src/Common/Common/Common.projitems b/src/Common/Common/Common.projitems new file mode 100644 index 0000000000..8786908294 --- /dev/null +++ b/src/Common/Common/Common.projitems @@ -0,0 +1,14 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 936af739-4297-4016-9d70-4280042709be + + + Microsoft.ApplicationInsights.Common + + + + + \ No newline at end of file diff --git a/src/Common/Common/Common.shproj b/src/Common/Common/Common.shproj new file mode 100644 index 0000000000..3bfdc9380e --- /dev/null +++ b/src/Common/Common/Common.shproj @@ -0,0 +1,13 @@ + + + + 936af739-4297-4016-9d70-4280042709be + 14.0 + + + + + + + + diff --git a/src/Common/Common/Extensions/ExceptionExtensions.cs b/src/Common/Common/Extensions/ExceptionExtensions.cs new file mode 100644 index 0000000000..c2627e9f39 --- /dev/null +++ b/src/Common/Common/Extensions/ExceptionExtensions.cs @@ -0,0 +1,40 @@ +namespace Microsoft.ApplicationInsights.Common.Extensions +{ + using System; + using System.Collections.Generic; + using System.Globalization; + + /// + /// Provides a set of extension methods for . + /// + internal static class ExceptionExtensions + { + /// + /// Concatenate the Message property of an Exception and any InnerExceptions. + /// + /// Exception to flatten. + /// Returns a concatenated string of exception messages. + public static string FlattenMessages(this Exception ex) + { + var list = new List(); + + for (var tempEx = ex; tempEx != null; tempEx = tempEx.InnerException) + { + list.Add(tempEx.Message); + } + + return string.Join(" | ", list); + } + + /// + /// Get a string representing an Exception. Includes Type and Message. + /// + /// Input exception. + /// Returns a string representing the exception. + public static string ToLogString(this Exception ex) + { + string msg = "Type: '{0}' Message: '{1}'"; + return string.Format(CultureInfo.InvariantCulture, msg, ex.GetType().ToString(), ex.FlattenMessages()); + } + } +} diff --git a/src/Microsoft.ApplicationInsights/ActivityExtensions.cs b/src/Microsoft.ApplicationInsights/ActivityExtensions.cs index 4efc82a664..b454a04f52 100644 --- a/src/Microsoft.ApplicationInsights/ActivityExtensions.cs +++ b/src/Microsoft.ApplicationInsights/ActivityExtensions.cs @@ -52,7 +52,7 @@ private static bool Initialize() { try { - Assembly.Load(new AssemblyName("System.Diagnostics.DiagnosticSource, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")); + Assembly.Load(new AssemblyName("System.Diagnostics.DiagnosticSource, Version=4.0.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")); return true; } catch (System.IO.FileNotFoundException) diff --git a/src/Microsoft.ApplicationInsights/Channel/InMemoryTransmitter.cs b/src/Microsoft.ApplicationInsights/Channel/InMemoryTransmitter.cs index 7d2501e7f5..bac18aaf5b 100644 --- a/src/Microsoft.ApplicationInsights/Channel/InMemoryTransmitter.cs +++ b/src/Microsoft.ApplicationInsights/Channel/InMemoryTransmitter.cs @@ -8,8 +8,10 @@ namespace Microsoft.ApplicationInsights.Channel using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; + using System.Net.Http; using System.Threading; using System.Threading.Tasks; + using Microsoft.ApplicationInsights.Common.Extensions; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; @@ -136,11 +138,11 @@ private void DequeueAndSend(TimeSpan timeout) try { // send request - this.Send(telemetryItems, timeout).Wait(); + this.Send(telemetryItems, timeout).GetAwaiter().GetResult(); } - catch (Exception e) + catch (Exception ex) { - CoreEventSource.Log.FailedToSend(e.Message); + CoreEventSource.Log.FailedToSend(ex.ToLogString()); } } } diff --git a/src/Microsoft.ApplicationInsights/DataContracts/DependencyTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/DependencyTelemetry.cs index 375b5d4899..84e065f6f8 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/DependencyTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/DependencyTelemetry.cs @@ -106,7 +106,7 @@ private DependencyTelemetry(DependencyTelemetry source) this.Sequence = source.Sequence; this.Timestamp = source.Timestamp; this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; this.successFieldSet = source.successFieldSet; this.extension = source.extension?.DeepClone(); this.Name = source.Name; @@ -327,7 +327,7 @@ public string DependencyKind public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.RemoteDependency; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } /// /// Gets or sets the MetricExtractorInfo. diff --git a/src/Microsoft.ApplicationInsights/DataContracts/EventTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/EventTelemetry.cs index 650c93210b..f101e939bb 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/EventTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/EventTelemetry.cs @@ -47,7 +47,7 @@ private EventTelemetry(EventTelemetry source) this.Sequence = source.Sequence; this.Timestamp = source.Timestamp; this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; this.extension = source.extension?.DeepClone(); } @@ -127,7 +127,7 @@ public string Name public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.Event; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } /// /// Deeply clones a object. diff --git a/src/Microsoft.ApplicationInsights/DataContracts/ExceptionTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/ExceptionTelemetry.cs index 27affdef7a..54b75ac455 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/ExceptionTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/ExceptionTelemetry.cs @@ -88,7 +88,7 @@ private ExceptionTelemetry(ExceptionTelemetry source) this.Sequence = source.Sequence; this.Timestamp = source.Timestamp; this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; if (!this.isCreatedFromExceptionInfo) { @@ -280,7 +280,7 @@ public string Message public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.Exception; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } internal IList Exceptions { diff --git a/src/Microsoft.ApplicationInsights/DataContracts/ISupportAdvancedSampling.cs b/src/Microsoft.ApplicationInsights/DataContracts/ISupportAdvancedSampling.cs index 142013d1ea..4979d1ffae 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/ISupportAdvancedSampling.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/ISupportAdvancedSampling.cs @@ -69,19 +69,40 @@ public enum SamplingTelemetryItemTypes Availability = 1024, } + /// + /// Represents sampling decision. + /// + public enum SamplingDecision + { + /// + /// Sampling decision has not been made. + /// + None = 0, + + /// + /// Item is sampled in. This may change as item flows through the pipeline. + /// + SampledIn = 1, + + /// + /// Item is sampled out. This may not change. + /// + SampledOut = 2, + } + /// /// Represent objects that support advanced sampling features. /// public interface ISupportAdvancedSampling : ISupportSampling { /// - /// Gets os sets the flag indicating item's telemetry type to consider in sampling evaluation. + /// Gets the flag indicating item's telemetry type to consider in sampling evaluation. /// SamplingTelemetryItemTypes ItemTypeFlag { get; } /// - /// Gets or sets a value indicating whether item was sampled out at head. + /// Gets or sets a value indicating whether item sampling decision was made pro-actively and result of this decision. /// - bool IsSampledOutAtHead { get; set; } + SamplingDecision ProactiveSamplingDecision { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights/DataContracts/PageViewPerformanceTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/PageViewPerformanceTelemetry.cs index 6cb49ca256..6e8a8dfbd2 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/PageViewPerformanceTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/PageViewPerformanceTelemetry.cs @@ -48,7 +48,7 @@ private PageViewPerformanceTelemetry(PageViewPerformanceTelemetry source) this.Context = source.Context.DeepClone(this.Data.properties); this.extension = source.extension?.DeepClone(); this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; } /// @@ -215,7 +215,7 @@ public TimeSpan ReceivedResponse public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.PageViewPerformance; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } /// /// Deeply clones a object. diff --git a/src/Microsoft.ApplicationInsights/DataContracts/PageViewTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/PageViewTelemetry.cs index 7070a725d8..042dee389d 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/PageViewTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/PageViewTelemetry.cs @@ -58,7 +58,7 @@ private PageViewTelemetry(PageViewTelemetry source) this.extension = source.extension?.DeepClone(); this.Timestamp = source.Timestamp; this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; } /// @@ -183,7 +183,7 @@ public TimeSpan Duration public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.PageView; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } /// /// Deeply clones a object. diff --git a/src/Microsoft.ApplicationInsights/DataContracts/RequestTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/RequestTelemetry.cs index 4289820635..b2d5ab66e1 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/RequestTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/RequestTelemetry.cs @@ -83,7 +83,7 @@ private RequestTelemetry(RequestTelemetry source) this.successFieldSet = source.successFieldSet; this.extension = source.extension?.DeepClone(); this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; } /// @@ -243,7 +243,7 @@ public string HttpMethod public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.Request; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } /// /// Gets or sets the source for the request telemetry object. This often is a hashed instrumentation key identifying the caller. diff --git a/src/Microsoft.ApplicationInsights/DataContracts/TraceTelemetry.cs b/src/Microsoft.ApplicationInsights/DataContracts/TraceTelemetry.cs index dacb2ef5e6..d8e95ca2ab 100644 --- a/src/Microsoft.ApplicationInsights/DataContracts/TraceTelemetry.cs +++ b/src/Microsoft.ApplicationInsights/DataContracts/TraceTelemetry.cs @@ -59,7 +59,7 @@ private TraceTelemetry(TraceTelemetry source) this.Sequence = source.Sequence; this.Timestamp = source.Timestamp; this.samplingPercentage = source.samplingPercentage; - this.IsSampledOutAtHead = source.IsSampledOutAtHead; + this.ProactiveSamplingDecision = source.ProactiveSamplingDecision; this.extension = source.extension?.DeepClone(); } @@ -139,7 +139,7 @@ public string Message public SamplingTelemetryItemTypes ItemTypeFlag => SamplingTelemetryItemTypes.Message; /// - public bool IsSampledOutAtHead { get; set; } = false; + public SamplingDecision ProactiveSamplingDecision { get; set; } /// /// Deeply clones a object. diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/IPlatform.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/IPlatform.cs index 9b30c835fd..545a5f4263 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/IPlatform.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/IPlatform.cs @@ -1,10 +1,6 @@ namespace Microsoft.ApplicationInsights.Extensibility.Implementation { - using System; - using System.Collections.Generic; - using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; - using Microsoft.ApplicationInsights.Extensibility.Implementation.External; /// /// Encapsulates platform-specific functionality required by the API. @@ -24,8 +20,13 @@ internal interface IPlatform /// IDebugOutput GetDebugOutput(); - // Read environment variable. - string GetEnvironmentVariable(string name); + /// + /// Find an environment variable by name. Will evaluate if that variable is empty. + /// + /// Name of environment variable. + /// Contains the value of the specified name. + /// Returns true if a non-empty value was found. + bool TryGetEnvironmentVariable(string name, out string value); /// /// Returns the machine name. diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/OperationHolder.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/OperationHolder.cs index 8f8e7a805f..f1ccee1755 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/OperationHolder.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/OperationHolder.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; + using Microsoft.ApplicationInsights.Extensibility.W3C; /// /// Operation class that holds the telemetry item and the corresponding telemetry client. @@ -17,6 +18,8 @@ internal class OperationHolder : IOperationHolder where T : OperationTelem private readonly TelemetryClient telemetryClient; + private readonly object originalActivity = null; + /// /// Indicates if this instance has been disposed of. /// @@ -28,10 +31,15 @@ internal class OperationHolder : IOperationHolder where T : OperationTelem /// /// Initializes telemetry client object. /// Operation telemetry item that is assigned to the telemetry associated to the current operation item. - public OperationHolder(TelemetryClient telemetryClient, T telemetry) + /// Original activity to restore after operation stops. Provide it if Activity created for the operation + /// is detached from the scope it was created in because custom Ids were provided by user. Null indicates that Activity was not detached + /// and no explicit restore is needed. It's passed around as object to allow ApplicationInsights.dll to be used in standalone mode + /// for backward compatibility. + public OperationHolder(TelemetryClient telemetryClient, T telemetry, object originalActivity = null) { this.telemetryClient = telemetryClient ?? throw new ArgumentNullException(nameof(telemetryClient)); this.Telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry)); + this.originalActivity = originalActivity; // it's ok if it's null } /// @@ -68,7 +76,9 @@ protected virtual void Dispose(bool disposing) isActivityAvailable = ActivityExtensions.TryRun(() => { var currentActivity = Activity.Current; - if (currentActivity == null || operationTelemetry.Id != currentActivity.Id) + if (currentActivity == null + || (Activity.DefaultIdFormat != ActivityIdFormat.W3C && operationTelemetry.Id != currentActivity.Id) + || (Activity.DefaultIdFormat == ActivityIdFormat.W3C && operationTelemetry.Id != W3CUtilities.FormatTelemetryId(currentActivity.TraceId.ToHexString(), currentActivity.SpanId.ToHexString()))) { // W3COperationCorrelationTelemetryInitializer changes Id // but keeps an original one in 'ai_legacyRequestId' property @@ -93,7 +103,14 @@ protected virtual void Dispose(bool disposing) this.telemetryClient.Track(operationTelemetry); - currentActivity.Stop(); + currentActivity?.Stop(); + + if (this.originalActivity != null && + Activity.Current != this.originalActivity && + this.originalActivity is Activity activity) + { + Activity.Current = activity; + } }); if (!isActivityAvailable) diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Platform/PlatformImplementation.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Platform/PlatformImplementation.cs index 9813becb44..f840a81067 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Platform/PlatformImplementation.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/Platform/PlatformImplementation.cs @@ -99,7 +99,8 @@ public IDebugOutput GetDebugOutput() return this.debugOutput ?? (this.debugOutput = new TelemetryDebugWriter()); } - public string GetEnvironmentVariable(string name) + /// + public bool TryGetEnvironmentVariable(string name, out string value) { if (string.IsNullOrWhiteSpace(name)) { @@ -107,7 +108,8 @@ public string GetEnvironmentVariable(string name) } object resultObj = this.environmentVariables?[name]; - return resultObj != null ? resultObj.ToString() : null; + value = resultObj?.ToString(); + return !string.IsNullOrEmpty(value); } /// @@ -181,9 +183,11 @@ public IDebugOutput GetDebugOutput() return this.debugOutput; } - public string GetEnvironmentVariable(string name) + /// + public bool TryGetEnvironmentVariable(string name, out string value) { - return Environment.GetEnvironmentVariable(name); + value = Environment.GetEnvironmentVariable(name); + return !string.IsNullOrEmpty(value); } /// diff --git a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/TelemetryConfigurationFactory.cs b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/TelemetryConfigurationFactory.cs index d28ad09af7..5ae620e280 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/Implementation/TelemetryConfigurationFactory.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/Implementation/TelemetryConfigurationFactory.cs @@ -78,8 +78,7 @@ public virtual void Initialize(TelemetryConfiguration configuration, TelemetryMo } // If an environment variable exists with an instrumentation key then use it (instead) for the "blackfield" scenario. - string environmentInstrumentationKey = PlatformSingleton.Current.GetEnvironmentVariable(InstrumentationKeyWebSitesEnvironmentVariable); - if (!string.IsNullOrEmpty(environmentInstrumentationKey)) + if (PlatformSingleton.Current.TryGetEnvironmentVariable(InstrumentationKeyWebSitesEnvironmentVariable, out string environmentInstrumentationKey)) { configuration.InstrumentationKey = environmentInstrumentationKey; } @@ -427,6 +426,10 @@ private static void LoadInstanceFromValue(XElement definition, Type expectedType { CoreEventSource.Log.LoadInstanceFromValueConfigurationError(definition.Name.LocalName, definition.Value, e.Message); } + catch (Exception ex) + { + throw new ArgumentException($"Failed to parse configuration value. Property: '{definition.Name.LocalName}' Reason: {ex.Message}", ex); + } } private static Type GetType(string typeName) diff --git a/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs b/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs index 877129cdce..89ea56deb5 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs @@ -6,82 +6,114 @@ using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Extensibility.Implementation; + using Microsoft.ApplicationInsights.Extensibility.W3C; -#if NET45 /// - /// Telemetry initializer that populates OperationContext for the telemetry item based on context stored in CallContext. + /// Telemetry initializer that populates OperationContext for the telemetry item from Activity. + /// This initializer is responsible for correlation of telemetry items within the same process. /// -#else - /// - /// Telemetry initializer that populates OperationContext for the telemetry item based on context stored in an AsyncLocal variable. - /// -#endif public class OperationCorrelationTelemetryInitializer : ITelemetryInitializer { + private const string TracestatePropertyKey = "tracestate"; + /// - /// Initializes/Adds operation id to the existing telemetry item. + /// Initializes/Adds operation context to the existing telemetry item. /// - /// Target telemetry item to add operation id. + /// Target telemetry item to add operation context. public void Initialize(ITelemetry telemetryItem) { - var itemContext = telemetryItem.Context.Operation; - var telemetryProp = telemetryItem as ISupportProperties; + var itemOperationContext = telemetryItem.Context.Operation; + var telemetryProp = telemetryItem as ISupportProperties; + bool isActivityAvailable = false; isActivityAvailable = ActivityExtensions.TryRun(() => - { + { var currentActivity = Activity.Current; - if (currentActivity != null) + if (currentActivity != null && string.IsNullOrEmpty(itemOperationContext.Id)) { - if (string.IsNullOrEmpty(itemContext.Id)) + if (currentActivity.IdFormat == ActivityIdFormat.W3C) { - itemContext.Id = currentActivity.RootId; + // Set OperationID to Activity.TraceId + // itemOperationContext.Id = currentActivity.RootId; // check if this can be used + itemOperationContext.Id = currentActivity.TraceId.ToHexString(); - if (string.IsNullOrEmpty(itemContext.ParentId)) + // Set OperationParentID to ID of parent, constructed as !traceid.spanid. + // ID for auto collected Request,Dependency are constructed as !traceid.spanid, so parentid must be set to the same format. + // While it is possible to set SpanID as the ID for auto collected Request,Dependency we have to stick to this format + // to maintain compatibility. This limitation may go away in the future. + if (string.IsNullOrEmpty(itemOperationContext.ParentId)) { - itemContext.ParentId = currentActivity.Id; + itemOperationContext.ParentId = W3CUtilities.FormatTelemetryId(itemOperationContext.Id, currentActivity.SpanId.ToHexString()); } - foreach (var baggage in currentActivity.Baggage) + // we are going to set tracestate property on requests and dependencies only + if (!string.IsNullOrEmpty(currentActivity.TraceStateString) && + telemetryItem is OperationTelemetry && + telemetryProp != null && + !telemetryProp.Properties.ContainsKey(TracestatePropertyKey)) { - if (telemetryProp != null && !telemetryProp.Properties.ContainsKey(baggage.Key)) - { - telemetryProp.Properties.Add(baggage); - } + telemetryProp.Properties.Add(TracestatePropertyKey, currentActivity.TraceStateString); } } + else + { + itemOperationContext.Id = currentActivity.RootId; - string operationName = currentActivity.GetOperationName(); + if (string.IsNullOrEmpty(itemOperationContext.ParentId)) + { + itemOperationContext.ParentId = currentActivity.Id; + } + } + + foreach (var baggage in currentActivity.Baggage) + { + if (telemetryProp != null && !telemetryProp.Properties.ContainsKey(baggage.Key)) + { + telemetryProp.Properties.Add(baggage); + } + } + + if (string.IsNullOrEmpty(itemOperationContext.Name)) + { + string operationName = currentActivity.GetOperationName(); + if (!string.IsNullOrEmpty(operationName)) + { + itemOperationContext.Name = operationName; + } + } - if (string.IsNullOrEmpty(itemContext.Name) && !string.IsNullOrEmpty(operationName)) + if (currentActivity.Recorded && + telemetryItem is ISupportAdvancedSampling supportSamplingTelemetry && + supportSamplingTelemetry.ProactiveSamplingDecision == SamplingDecision.None) { - itemContext.Name = operationName; + supportSamplingTelemetry.ProactiveSamplingDecision = SamplingDecision.SampledIn; } } }); if (!isActivityAvailable) { - if (string.IsNullOrEmpty(itemContext.ParentId) || string.IsNullOrEmpty(itemContext.Id) || string.IsNullOrEmpty(itemContext.Name)) + if (string.IsNullOrEmpty(itemOperationContext.ParentId) || string.IsNullOrEmpty(itemOperationContext.Id) || string.IsNullOrEmpty(itemOperationContext.Name)) { var parentContext = CallContextHelpers.GetCurrentOperationContext(); if (parentContext != null) { - if (string.IsNullOrEmpty(itemContext.ParentId) + if (string.IsNullOrEmpty(itemOperationContext.ParentId) && !string.IsNullOrEmpty(parentContext.ParentOperationId)) { - itemContext.ParentId = parentContext.ParentOperationId; + itemOperationContext.ParentId = parentContext.ParentOperationId; } - if (string.IsNullOrEmpty(itemContext.Id) + if (string.IsNullOrEmpty(itemOperationContext.Id) && !string.IsNullOrEmpty(parentContext.RootOperationId)) { - itemContext.Id = parentContext.RootOperationId; + itemOperationContext.Id = parentContext.RootOperationId; } - if (string.IsNullOrEmpty(itemContext.Name) + if (string.IsNullOrEmpty(itemOperationContext.Name) && !string.IsNullOrEmpty(parentContext.RootOperationName)) { - itemContext.Name = parentContext.RootOperationName; + itemOperationContext.Name = parentContext.RootOperationName; } if (parentContext.CorrelationContext != null) diff --git a/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs b/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs index 36d3b040ee..cd931f1ae3 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/TelemetryConfiguration.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; + using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights.Channel; @@ -26,7 +27,7 @@ public sealed class TelemetryConfiguration : IDisposable internal readonly SamplingRateStore LastKnownSampleRateStore = new SamplingRateStore(); private static object syncRoot = new object(); - private static TelemetryConfiguration active; + private static TelemetryConfiguration active; private readonly SnapshottingList telemetryInitializers = new SnapshottingList(); private readonly TelemetrySinkCollection telemetrySinks = new TelemetrySinkCollection(); @@ -42,6 +43,25 @@ public sealed class TelemetryConfiguration : IDisposable /// private bool isDisposed = false; + /// + /// Static Constructor which sets ActivityID Format to W3C if Format not enforced. + /// This ensures SDK operates in W3C mode, unless turned off explicitily with the following 2 lines + /// in user code in application startup. + /// Activity.DefaultIdFormat = ActivityIdFormat.Hierarchical + /// Activity.ForceDefaultIdFormat = true. + /// + static TelemetryConfiguration() + { + ActivityExtensions.TryRun(() => + { + if (!Activity.ForceDefaultIdFormat) + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + Activity.ForceDefaultIdFormat = true; + } + }); + } + /// /// Initializes a new instance of the TelemetryConfiguration class. /// diff --git a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CActivityExtensions.cs b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CActivityExtensions.cs index 0970ba1781..8d934799c9 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CActivityExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CActivityExtensions.cs @@ -15,30 +15,19 @@ /// Extends Activity to support W3C distributed tracing standard. /// [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C making extension methods in this class no longer required.")] public static class W3CActivityExtensions { - private const string RddDiagnosticSourcePrefix = "rdddsc"; - private const string SqlRemoteDependencyType = "SQL"; - - private static readonly Regex TraceIdRegex = new Regex("^[a-f0-9]{32}$", RegexOptions.Compiled); - private static readonly Regex SpanIdRegex = new Regex("^[a-f0-9]{16}$", RegexOptions.Compiled); - /// /// Generate new W3C context. /// /// Activity to generate W3C context on. /// The same Activity for chaining. [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] public static Activity GenerateW3CContext(this Activity activity) { - activity.SetVersion(W3CConstants.DefaultVersion); - activity.SetSampled(W3CConstants.TraceFlagRecordedAndNotRequested); - activity.SetSpanId(W3CUtilities.GenerateSpanId()); - - activity.SetTraceId(activity.RootId != null && TraceIdRegex.IsMatch(activity.RootId) - ? activity.RootId - : W3CUtilities.GenerateTraceId()); - + // No-op return activity; } @@ -48,9 +37,10 @@ public static Activity GenerateW3CContext(this Activity activity) /// Activity to check. /// True if Activity has W3C properties, false otherwise. [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] public static bool IsW3CActivity(this Activity activity) { - return activity != null && activity.Tags.Any(t => t.Key == W3CConstants.TraceIdTag); + return activity != null && activity.IdFormat == ActivityIdFormat.W3C; } /// @@ -59,18 +49,11 @@ public static bool IsW3CActivity(this Activity activity) /// Activity to update W3C context on. /// The same Activity for chaining. [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] public static Activity UpdateContextOnActivity(this Activity activity) { - if (activity == null || activity.Tags.Any(t => t.Key == W3CConstants.TraceIdTag)) - { - return activity; - } - - // no w3c Tags on Activity - activity.Parent.UpdateContextOnActivity(); - - // at this point, Parent has W3C tags, but current activity does not - update it - return activity.UpdateContextFromParent(); + // No-op + return activity; } /// @@ -79,34 +62,11 @@ public static Activity UpdateContextOnActivity(this Activity activity) /// Activity to read W3C context from. /// traceparent header value. [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] public static string GetTraceparent(this Activity activity) { - string version = null, traceId = null, spanId = null, sampled = null; - foreach (var tag in activity.Tags) - { - switch (tag.Key) - { - case W3CConstants.TraceIdTag: - traceId = tag.Value; - break; - case W3CConstants.SpanIdTag: - spanId = tag.Value; - break; - case W3CConstants.VersionTag: - version = tag.Value; - break; - case W3CConstants.SampledTag: - sampled = tag.Value; - break; - } - } - - if (traceId == null || spanId == null || version == null || sampled == null) - { - return null; - } - - return string.Join("-", version, traceId, spanId, sampled); + // Activity.ID is the trasceparent header. + return activity.Id; } /// @@ -115,58 +75,10 @@ public static string GetTraceparent(this Activity activity) /// Activity to set W3C context on. /// Valid traceparent header like 00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01. [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] public static void SetTraceparent(this Activity activity, string value) { - if (activity.IsW3CActivity()) - { - return; - } - - // we only support 00 version and ignore caller version - activity.SetVersion(W3CConstants.DefaultVersion); - - string traceId = null, parentSpanId = null, sampledStr = null; - bool isValid = false; - - var parts = value?.Split('-'); - if (parts != null && parts.Length == 4) - { - traceId = parts[1]; - parentSpanId = parts[2]; - sampledStr = parts[3]; - isValid = TraceIdRegex.IsMatch(traceId) && SpanIdRegex.IsMatch(parentSpanId); - } - - if (isValid) - { - byte.TryParse(sampledStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var sampled); - - // we always defer sampling - if ((sampled & W3CConstants.RequestedTraceFlag) == W3CConstants.RequestedTraceFlag) - { - activity.SetSampled(W3CConstants.TraceFlagRecordedAndRequested); - } - else - { - activity.SetSampled(W3CConstants.TraceFlagRecordedAndNotRequested); - } - - activity.SetParentSpanId(parentSpanId); - activity.SetSpanId(W3CUtilities.GenerateSpanId()); - activity.SetTraceId(traceId); - } - else - { - activity.SetSampled(W3CConstants.TraceFlagRecordedAndNotRequested); - activity.SetSpanId(W3CUtilities.GenerateSpanId()); - activity.SetTraceId(W3CUtilities.GenerateTraceId()); - } - - if (activity.Id == null) - { - // activity is not started yet - activity.SetParentId(string.Concat("|", activity.GetTraceId(), ".", activity.GetParentSpanId(), ".")); - } + // no-op } /// @@ -175,44 +87,59 @@ public static void SetTraceparent(this Activity activity, string value) /// Activity to get tracestate from. /// tracestate header value. [EditorBrowsable(EditorBrowsableState.Never)] - public static string GetTracestate(this Activity activity) => - activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.TracestateTag).Value; - + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] + public static string GetTracestate(this Activity activity) + { + return activity.TraceStateString; + } + /// /// Sets tracestate header value on the Activity. /// /// Activity to set tracestate on. /// tracestate header value. [EditorBrowsable(EditorBrowsableState.Never)] - public static void SetTracestate(this Activity activity, string value) => - activity.AddTag(W3CConstants.TracestateTag, value); + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] + public static void SetTracestate(this Activity activity, string value) + { + activity.TraceStateString = value; + } /// /// Gets TraceId from the Activity. - /// Use carefully: if may cause iteration over all tags!. /// /// Activity to get traceId from. /// TraceId value or null if it does not exist. [EditorBrowsable(EditorBrowsableState.Never)] - public static string GetTraceId(this Activity activity) => activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.TraceIdTag).Value; + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C. Use Activity.TraceId to get Trace ID")] + public static string GetTraceId(this Activity activity) + { + return activity.TraceId.ToHexString(); + } /// - /// Gets SpanId from the Activity. - /// Use carefully: if may cause iteration over all tags!. + /// Gets SpanId from the Activity. /// /// Activity to get spanId from. /// SpanId value or null if it does not exist. [EditorBrowsable(EditorBrowsableState.Never)] - public static string GetSpanId(this Activity activity) => activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.SpanIdTag).Value; + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C. Use Activity.SpanId to get Span ID")] + public static string GetSpanId(this Activity activity) + { + return activity.SpanId.ToHexString(); + } /// /// Gets ParentSpanId from the Activity. - /// Use carefully: if may cause iteration over all tags!. /// /// Activity to get ParentSpanId from. /// ParentSpanId value or null if it does not exist. [EditorBrowsable(EditorBrowsableState.Never)] - public static string GetParentSpanId(this Activity activity) => activity.Tags.FirstOrDefault(t => t.Key == W3CConstants.ParentSpanIdTag).Value; + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C. Use Activity.ParentSpanId to get ParentSpan ID")] + public static string GetParentSpanId(this Activity activity) + { + return activity.ParentSpanId.ToHexString(); + } /// /// Sets Activity W3C context on the telemetry. @@ -221,165 +148,17 @@ public static void SetTraceparent(this Activity activity, string value) /// Telemetry item. /// Force update if properties are already set. [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = - "This method has different code for Net45/NetCore")] + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C. OperationCorrelationTelemetryInitializer is W3C aware and is recommended to update telemetry from current Activity.")] public static void UpdateTelemetry(this Activity activity, ITelemetry telemetry, bool forceUpdate) { - if (activity == null) - { - return; - } - - activity.UpdateContextOnActivity(); - - // Requests and dependencies are initialized from the current Activity - // (i.e. telemetry.Id = current.Id). Activity is created for such requests specifically - // Traces, exceptions, events on the other side are children of current activity - // There is one exception - SQL DiagnosticSource where current Activity is a parent - // for dependency calls. - - OperationTelemetry opTelemetry = telemetry as OperationTelemetry; - bool initializeFromCurrent = opTelemetry != null; - - if (initializeFromCurrent) - { - initializeFromCurrent &= !(opTelemetry is DependencyTelemetry dependency && - dependency.Type == SqlRemoteDependencyType && - dependency.Context.GetInternalContext().SdkVersion - .StartsWith(RddDiagnosticSourcePrefix, StringComparison.Ordinal)); - } - - string spanId = null, parentSpanId = null; - foreach (var tag in activity.Tags) - { - switch (tag.Key) - { - case W3CConstants.TraceIdTag: - telemetry.Context.Operation.Id = tag.Value; - break; - case W3CConstants.SpanIdTag: - spanId = tag.Value; - break; - case W3CConstants.ParentSpanIdTag: - parentSpanId = tag.Value; - break; - case W3CConstants.TracestateTag: - if (telemetry is OperationTelemetry operation) - { - operation.Properties[W3CConstants.TracestateTag] = tag.Value; - } - - break; - } - } - - if (initializeFromCurrent) - { -#if NET45 || NET46 - // on .NET Fx Activities are not always reliable, this code prevents update - // of the telemetry that was forcibly updated during Activity lifetime - // ON .NET Core there is no such problem - // if spanId is valid already and update is not forced, ignore it - if (!forceUpdate && IsValidTelemetryId(opTelemetry.Id, telemetry.Context.Operation.Id)) - { - return; - } -#endif - opTelemetry.Id = FormatRequestId(telemetry.Context.Operation.Id, spanId); - if (parentSpanId != null) - { - telemetry.Context.Operation.ParentId = - FormatRequestId(telemetry.Context.Operation.Id, parentSpanId); - } - } - else - { - telemetry.Context.Operation.ParentId = - FormatRequestId(telemetry.Context.Operation.Id, spanId); - } - - if (opTelemetry != null) - { - if (opTelemetry.Context.Operation.Id != activity.RootId) - { - opTelemetry.Properties[W3CConstants.LegacyRootIdProperty] = activity.RootId; - } - - if (opTelemetry.Id != activity.Id) - { - opTelemetry.Properties[W3CConstants.LegacyRequestIdProperty] = activity.Id; - } - } + // no-op } [EditorBrowsable(EditorBrowsableState.Never)] - internal static void SetParentSpanId(this Activity activity, string value) => - activity.AddTag(W3CConstants.ParentSpanIdTag, value); - - private static void SetTraceId(this Activity activity, string value) => - activity.AddTag(W3CConstants.TraceIdTag, value); - - private static void SetSpanId(this Activity activity, string value) => - activity.AddTag(W3CConstants.SpanIdTag, value); - - private static void SetVersion(this Activity activity, string value) => - activity.AddTag(W3CConstants.VersionTag, value); - - private static void SetSampled(this Activity activity, string value) => - activity.AddTag(W3CConstants.SampledTag, value); - - private static Activity UpdateContextFromParent(this Activity activity) - { - if (activity != null && activity.Tags.All(t => t.Key != W3CConstants.TraceIdTag)) - { - if (activity.Parent == null) - { - activity.GenerateW3CContext(); - } - else - { - foreach (var tag in activity.Parent.Tags) - { - switch (tag.Key) - { - case W3CConstants.TraceIdTag: - activity.SetTraceId(tag.Value); - break; - case W3CConstants.SpanIdTag: - activity.SetParentSpanId(tag.Value); - activity.SetSpanId(W3CUtilities.GenerateSpanId()); - break; - case W3CConstants.VersionTag: - activity.SetVersion(tag.Value); - break; - case W3CConstants.SampledTag: - activity.SetSampled(tag.Value); - break; - case W3CConstants.TracestateTag: - activity.SetTracestate(tag.Value); - break; - } - } - } - } - - return activity; - } - -#if NET45 || NET46 - private static bool IsValidTelemetryId(string id, string operationId) - { - return id.Length == 51 && - id[0] == '|' && - id[33] == '.' && - id.IndexOf('.', 34) == 50 && - id.IndexOf(operationId, 1, 33, StringComparison.Ordinal) == 1; - } -#endif - - private static string FormatRequestId(string traceId, string spanId) + [Obsolete("Activity from System.Diagnostics.DiagnosticSource 4.6.0 onwards natively support W3C.")] + internal static void SetParentSpanId(this Activity activity, string value) { - return string.Concat("|", traceId, ".", spanId, "."); - } + // no-op + } } } diff --git a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CConstants.cs b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CConstants.cs index 2a1dacb799..0467663979 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CConstants.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CConstants.cs @@ -1,68 +1,16 @@ namespace Microsoft.ApplicationInsights.Extensibility.W3C { + using System; + using System.Diagnostics; + /// /// W3C constants. /// internal static class W3CConstants { - /// - /// Trace-Id tag name. - /// - internal const string TraceIdTag = "w3c_traceId"; - - /// - /// Span-Id tag name. - /// - internal const string SpanIdTag = "w3c_spanId"; - - /// - /// Parent span-Id tag name. - /// - internal const string ParentSpanIdTag = "w3c_parentSpanId"; - - /// - /// Version tag name. - /// - internal const string VersionTag = "w3c_version"; - - /// - /// Sampled tag name. - /// - internal const string SampledTag = "w3c_sampled"; - - /// - /// Tracestate tag name. - /// - internal const string TracestateTag = "w3c_tracestate"; - - /// - /// Default version value. - /// - internal const string DefaultVersion = "00"; - - /// - /// Default sampled flag value: may be recorded, not requested. - /// - internal const string TraceFlagRecordedAndNotRequested = "02"; - - /// - /// Recorded and requested sampled flag value. - /// - internal const string TraceFlagRecordedAndRequested = "03"; - - /// - /// Requested trace flag. - /// - internal const byte RequestedTraceFlag = 1; - /// /// Legacy root Id tag name. /// internal const string LegacyRootIdProperty = "ai_legacyRootId"; - - /// - /// Legacy root Id tag name. - /// - internal const string LegacyRequestIdProperty = "ai_legacyRequestId"; } } diff --git a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3COperationCorrelationTelemetryInitializer.cs b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3COperationCorrelationTelemetryInitializer.cs index 044280c04e..5f2ba16f6e 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3COperationCorrelationTelemetryInitializer.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3COperationCorrelationTelemetryInitializer.cs @@ -1,5 +1,6 @@ namespace Microsoft.ApplicationInsights.Extensibility.W3C { + using System; using System.ComponentModel; using System.Diagnostics; using Microsoft.ApplicationInsights.Channel; @@ -9,6 +10,7 @@ /// Telemetry Initializer that sets correlation ids for W3C. /// [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Obsolete in favor of OperationCorrelationTelemetryInitializer which is now W3C aware.")] public class W3COperationCorrelationTelemetryInitializer : ITelemetryInitializer { /// @@ -17,8 +19,8 @@ public class W3COperationCorrelationTelemetryInitializer : ITelemetryInitializer /// Telemetry item. public void Initialize(ITelemetry telemetry) { - Activity currentActivity = Activity.Current; - currentActivity.UpdateTelemetry(telemetry, false); + // No op. This is no longer needed as OperationCorrelationTelemetryInitializer does the job. + // Since this class was part of Public API, not removing this, but keeping it no-op } } } diff --git a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CUtilities.cs b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CUtilities.cs index 61c1bca422..d17ada9f83 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CUtilities.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/W3C/W3CUtilities.cs @@ -2,7 +2,9 @@ { using System; using System.ComponentModel; + using System.Diagnostics; using System.Globalization; + using System.Text.RegularExpressions; using Microsoft.ApplicationInsights.Extensibility.Implementation; /// @@ -12,6 +14,7 @@ public static class W3CUtilities { private static readonly uint[] Lookup32 = CreateLookup32(); + private static readonly Regex TraceIdRegex = new Regex("^[a-f0-9]{32}$", RegexOptions.Compiled); /// /// Generates random trace Id as per W3C Distributed tracing specification. @@ -19,12 +22,30 @@ public static class W3CUtilities /// /// Random 16 bytes array encoded as hex string. [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use ActivityTraceId.CreateRandom().ToHexString() instead.")] public static string GenerateTraceId() { - byte[] firstHalf = BitConverter.GetBytes(WeakConcurrentRandom.Instance.Next()); - byte[] secondHalf = BitConverter.GetBytes(WeakConcurrentRandom.Instance.Next()); + return ActivityTraceId.CreateRandom().ToHexString(); + } - return GenerateId(firstHalf, secondHalf, 0, 16); + /// + /// Constructs a Telemetry ID from given traceid and span id in the format |traceid.spanid. + /// This is the format used by Application Insights. + /// + /// constructed Telemetry ID. + internal static string FormatTelemetryId(string traceId, string spanId) + { + return string.Concat("|", traceId, ".", spanId, "."); + } + + /// + /// Checks if the given string is a valid trace-id as per W3C Specs. + /// https://github.com/w3c/distributed-tracing/blob/master/trace_context/HTTP_HEADER_FORMAT.md#trace-id . + /// + /// true if valid w3c trace id, otherwise false. + internal static bool IsCompatibleW3CTraceId(string traceId) + { + return TraceIdRegex.IsMatch(traceId); } /// @@ -56,25 +77,6 @@ private static string GenerateId(byte[] bytes, int start, int length) return new string(result); } - /// - /// Converts byte arrays to hex lower case string. - /// - /// Array encoded as hex string. - private static string GenerateId(byte[] firstHalf, byte[] secondHalf, int start, int length) - { - // See https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727 - var result = new char[length * 2]; - int arrayBorder = length / 2; - for (int i = start; i < start + length; i++) - { - var val = Lookup32[i < arrayBorder ? firstHalf[i] : secondHalf[i - arrayBorder]]; - result[2 * i] = (char)val; - result[(2 * i) + 1] = (char)(val >> 16); - } - - return new string(result); - } - private static uint[] CreateLookup32() { // See https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa/24343727#24343727 diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/AggregationPeriodSummary.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/AggregationPeriodSummary.cs index 9904c962fd..f03be84a1a 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/AggregationPeriodSummary.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/AggregationPeriodSummary.cs @@ -3,23 +3,23 @@ using System; using System.Collections.Generic; - /// @ToDo: Complete documentation before stable release. {390}. + /// A container used by a to hold all aggregates resukting from a particular aggregation period. /// @PublicExposureCandidate internal class AggregationPeriodSummary { - /// @ToDo: Complete documentation before stable release. {487}. - /// @ToDo: Complete documentation before stable release. {672}. - /// @ToDo: Complete documentation before stable release. {290}. + /// Creates a new AggregationPeriodSummary. + /// Persistent aggregators carry forward their state across aggregation cycles. + /// Non-persistent aggregators do not keep state from previous aggregation cycles. public AggregationPeriodSummary(IReadOnlyList persistentAggregates, IReadOnlyList nonpersistentAggregates) { this.PersistentAggregates = persistentAggregates; this.NonpersistentAggregates = nonpersistentAggregates; } - /// Gets @ToDo: Complete documentation before stable release. {315}. + /// Gets persistent aggregators, which carry forward their state across aggregation cycles. public IReadOnlyList PersistentAggregates { get; } - /// Gets @ToDo: Complete documentation before stable release. {776}. + /// Gets Non-persistent aggregators, which do not keep state from previous aggregation cycles. public IReadOnlyList NonpersistentAggregates { get; } } } diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/ApplicationInsightsTelemetryPipeline.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/ApplicationInsightsTelemetryPipeline.cs index d41de6d673..3516ad4c4a 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/ApplicationInsightsTelemetryPipeline.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/ApplicationInsightsTelemetryPipeline.cs @@ -6,15 +6,15 @@ using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {628}. + /// An adapter that represents the Application Insights SDK pipelie towards the Metrics Aggregation SDK subsystem. /// @PublicExposureCandidate internal class ApplicationInsightsTelemetryPipeline : IMetricTelemetryPipeline { private readonly ApplicationInsights.TelemetryClient trackingClient; private readonly Task completedTask = Task.FromResult(true); - /// @ToDo: Complete documentation before stable release. {763}. - /// @ToDo: Complete documentation before stable release. {887}. + /// Creaes a new Application Insights telemetry pipeline adapter. + /// The Application Insights telemetry pipeline to be adapted. public ApplicationInsightsTelemetryPipeline(ApplicationInsights.Extensibility.TelemetryConfiguration telemetryPipeline) { Util.ValidateNotNull(telemetryPipeline, nameof(telemetryPipeline)); @@ -22,8 +22,8 @@ public ApplicationInsightsTelemetryPipeline(ApplicationInsights.Extensibility.Te this.trackingClient = new ApplicationInsights.TelemetryClient(telemetryPipeline); } - /// @ToDo: Complete documentation before stable release. {253}. - /// @ToDo: Complete documentation before stable release. {017}. + /// Creaes a new Application Insights telemetry pipeline adapter. + /// The Application Insights telemetry pipeline to be adapted. public ApplicationInsightsTelemetryPipeline(ApplicationInsights.TelemetryClient telemetryClient) { Util.ValidateNotNull(telemetryClient, nameof(telemetryClient)); @@ -31,10 +31,16 @@ public ApplicationInsightsTelemetryPipeline(ApplicationInsights.TelemetryClient this.trackingClient = telemetryClient; } - /// @ToDo: Complete documentation before stable release. {017}. - /// @ToDo: Complete documentation before stable release. {043}. - /// @ToDo: Complete documentation before stable release. {921}. - /// @ToDo: Complete documentation before stable release. {373}. + /// + /// Send a metric aggregate to the cloud using the local Application Insights pipeline. + /// + /// The aggregate. + /// Cancellation is not supported by the underlying pipeline, but it is respected be this method. + /// The specified metricAggregate is null. + /// The runtime class of the specified metricAggregate does not match the + /// telemetry destination type represented by this instance of IMetricTelemetryPipeline. + /// The specified cancelToken has had cancellation requested. + /// The task representing the Track operation. public Task TrackAsync(MetricAggregate metricAggregate, CancellationToken cancelToken) { Util.ValidateNotNull(metricAggregate, nameof(metricAggregate)); @@ -63,9 +69,9 @@ public Task TrackAsync(MetricAggregate metricAggregate, CancellationToken cancel return this.completedTask; } - /// @ToDo: Complete documentation before stable release. {935}. - /// @ToDo: Complete documentation before stable release. {490}. - /// @ToDo: Complete documentation before stable release. {817}. + /// Flushes the Application Insights pipeline used by this adaptor. + /// Cancellation is not supported by the underlying pipeline, but it is respected be this method. + /// The task representing the Flush operation. public Task FlushAsync(CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricAggregateToTelemetryPipelineConverter.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricAggregateToTelemetryPipelineConverter.cs index 25f79e751f..c118033337 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricAggregateToTelemetryPipelineConverter.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricAggregateToTelemetryPipelineConverter.cs @@ -2,13 +2,13 @@ { using System; - /// @ToDo: Complete documentation before stable release. {397}. + /// Abstraction for data converters between items and contracts supported by a ingestion pipeline. /// @PublicExposureCandidate internal interface IMetricAggregateToTelemetryPipelineConverter { - /// @ToDo: Complete documentation before stable release. {360}. - /// @ToDo: Complete documentation before stable release. {116}. - /// @ToDo: Complete documentation before stable release. {685}. + /// Convert a MetricAggregate to a an exchange type supported by an ingestion pipeline. + /// The aggregate to convert. + /// Converted object. object Convert(MetricAggregate aggregate); } } diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesAggregator.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesAggregator.cs index db23d6ca85..1f75c56476 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesAggregator.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesAggregator.cs @@ -2,42 +2,46 @@ { using System; - /// @ToDo: Complete documentation before stable release. {558}. + /// The abstraction for a metric aggregator. + /// An aggregator is a data processing type that inspects all values tracked for a metric series across an aggregation + /// period and creates an aggregate that summarizes the period when it is completed. The most common aggregator is + /// the , which produces aggregates that contain the Min, Max, Sum and + /// Count of values tracked over the aggregation time period. public interface IMetricSeriesAggregator { - /// Gets @ToDo: Complete documentation before stable release. {969}. + /// Gets the data series aggregated by this aggregator. MetricSeries DataSeries { get; } - /// @ToDo: Complete documentation before stable release. {792}. - /// @ToDo: Complete documentation before stable release. {235}. + /// Attempts to reset this aggregator so it ban be reused for a new aggregation period. + /// Whether the reset was successful (if not, this aggregator may not be reused). bool TryRecycle(); - /// @ToDo: Complete documentation before stable release. {246}. - /// @ToDo: Complete documentation before stable release. {781}. - /// @ToDo: Complete documentation before stable release. {567}. + /// Resets this aggregator and prepares it for a new aggregation period. + /// The start of the new aggregation period. + /// The filter for the values to be used. /// @PublicExposureCandidate void Reset(DateTimeOffset periodStart, IMetricValueFilter valueFilter); - /// @ToDo: Complete documentation before stable release. {734}. - /// @ToDo: Complete documentation before stable release. {299}. + /// Resets this aggregator and prepares it for a new aggregation period. + /// The start of the new aggregation period. void Reset(DateTimeOffset periodStart); - /// @ToDo: Complete documentation before stable release. {099}. - /// @ToDo: Complete documentation before stable release. {193}. - /// @ToDo: Complete documentation before stable release. {573}. + /// Wraps up the ongping aggregation period and procudes the resulting aggregate. + /// The end timestamp of the period. + /// The aggregate containing the sumary of the completed period. MetricAggregate CompleteAggregation(DateTimeOffset periodEnd); - /// @ToDo: Complete documentation before stable release. {221}. - /// @ToDo: Complete documentation before stable release. {203}. - /// @ToDo: Complete documentation before stable release. {615}. + /// Creates the aggregate for the ongoing aggregation period without completing the period. May not be thread safe. + /// The ent timestamp for the aggregate. + /// An aggregate representing the ongoing period so far. MetricAggregate CreateAggregateUnsafe(DateTimeOffset periodEnd); - /// @ToDo: Complete documentation before stable release. {574}. - /// @ToDo: Complete documentation before stable release. {887}. + /// Adds a value to the aggregation. + /// Metric value to be tracked. void TrackValue(double metricValue); - /// @ToDo: Complete documentation before stable release. {206}. - /// @ToDo: Complete documentation before stable release. {266}. + /// Adds a value to the aggregation. + /// Metric value to be tracked. void TrackValue(object metricValue); } } diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesFilter.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesFilter.cs index 8c1ac7762b..ebcb1ac422 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesFilter.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricSeriesFilter.cs @@ -2,14 +2,14 @@ { using System; - /// @ToDo: Complete documentation before stable release. {339}. + /// A filter that determines whether a series is being tracked. /// @PublicExposureCandidate internal interface IMetricSeriesFilter { - /// @ToDo: Complete documentation before stable release. {600}. - /// @ToDo: Complete documentation before stable release. {025}. - /// @ToDo: Complete documentation before stable release. {050}. - /// @ToDo: Complete documentation before stable release. {100}. + /// Determine if a series is being tracked and fetch the rspective value filter. + /// A metric data series. + /// A values filter used for this series (if any) or null. + /// WHether a series is being tracked. bool WillConsume(MetricSeries dataSeries, out IMetricValueFilter valueFilter); } } \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricTelemetryPipeline.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricTelemetryPipeline.cs index 96b90d1a9f..8bf681ec19 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricTelemetryPipeline.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricTelemetryPipeline.cs @@ -19,12 +19,12 @@ public interface IMetricTelemetryPipeline /// The runtime class of the specified metricAggregate does not match the /// telemetry destination type represented by this instance of IMetricTelemetryPipeline. /// The specified cancelToken has had cancellation requested. - /// @ToDo: Complete documentation before stable release. {034}. + /// The task representing the Track operation. Task TrackAsync(MetricAggregate metricAggregate, CancellationToken cancelToken); - /// @ToDo: Complete documentation before stable release. {453}. - /// @ToDo: Complete documentation before stable release. {697}. - /// @ToDo: Complete documentation before stable release. {934}. + /// Flushes the telemetry pipeline in case it had cached any data. + /// Cancellation may or may not be supported by different destinations. + /// The task representing the Flush operation. Task FlushAsync(CancellationToken cancelToken); } } diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricValueFilter.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricValueFilter.cs index dfbd5a91d2..eaca6faee4 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricValueFilter.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/IMetricValueFilter.cs @@ -1,18 +1,18 @@ namespace Microsoft.ApplicationInsights.Metrics.Extensibility { - /// @ToDo: Complete documentation before stable release. {265}. + /// Abstraction for a filter that can controll whether values are being tracked or ignored by a metric aggregator. public interface IMetricValueFilter { - /// @ToDo: Complete documentation before stable release. {919}. - /// @ToDo: Complete documentation before stable release. {526}. - /// @ToDo: Complete documentation before stable release. {095}. - /// @ToDo: Complete documentation before stable release. {464}. + /// Determine whether a value will be tracked or ignored while aggregating a metric data time series. + /// A metric data time series. + /// A metric value. + /// Whether or not a value will be tracked or ignored while aggregating a metric data time series. bool WillConsume(MetricSeries dataSeries, double metricValue); - /// @ToDo: Complete documentation before stable release. {704}. - /// @ToDo: Complete documentation before stable release. {929}. - /// @ToDo: Complete documentation before stable release. {703}. - /// @ToDo: Complete documentation before stable release. {497}. + /// Determine whether a value will be tracked or ignored while aggregating a metric data time series. + /// A metric data time series. + /// A metric value. + /// Whether or not a value will be tracked or ignored while aggregating a metric data time series. bool WillConsume(MetricSeries dataSeries, object metricValue); } } \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MemoryMetricTelemetryPipeline.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MemoryMetricTelemetryPipeline.cs index ae828b5444..ecc693cf13 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MemoryMetricTelemetryPipeline.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MemoryMetricTelemetryPipeline.cs @@ -6,12 +6,15 @@ using System.Threading; using System.Threading.Tasks; - /// @ToDo: Complete documentation before stable release. {079}. + /// A IMetricTelemetryPipeline that holds aggregates in memory instead of sending them anywhere for processing. + /// This is useful for local testing and debugging scenarios. + /// An instance of this class holds up to aggregates in memory. WHen additional aggregates are written, + /// the oldest ones get discarded. /// @PublicExposureCandidate [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001: Types that own disposable fields should be disposable", Justification = "OK not to explicitly dispose a released SemaphoreSlim.")] internal class MemoryMetricTelemetryPipeline : IMetricTelemetryPipeline, IReadOnlyList { - /// @ToDo: Complete documentation before stable release. {529}. + /// Default setting for how many items to hold in memory. public const int CountLimitDefault = 1000; // private readonly Task completedTask = Task.FromResult(true); @@ -19,14 +22,14 @@ internal class MemoryMetricTelemetryPipeline : IMetricTelemetryPipeline, IReadOn private readonly IList metricAgregates = new List(); - /// @ToDo: Complete documentation before stable release. {846}. + /// Creates a new MemoryMetricTelemetryPipeline that holds up to CountLimitDefault aggregates in memory. public MemoryMetricTelemetryPipeline() : this(CountLimitDefault) { } - /// @ToDo: Complete documentation before stable release. {195}. - /// @ToDo: Complete documentation before stable release. {153}. + /// Creates a new MemoryMetricTelemetryPipeline that holds up to the specified number of aggregates in memory. + /// Max number of most recent aggregates to hold in memory. public MemoryMetricTelemetryPipeline(int countLimit) { if (countLimit <= 0) @@ -37,10 +40,10 @@ public MemoryMetricTelemetryPipeline(int countLimit) this.CountLimit = countLimit; } - /// Gets @ToDo: Complete documentation before stable release. {953}. + /// Gets the max buffer size. public int CountLimit { get; } - /// Gets @ToDo: Complete documentation before stable release. {917}. + /// Gets the current number of metric aggregates in the buffer. public int Count { get @@ -60,9 +63,9 @@ public int Count } } - /// @ToDo: Complete documentation before stable release. {823}. - /// @ToDo: Complete documentation before stable release. {470}. - /// @ToDo: Complete documentation before stable release. {699}. + /// Provides access to the metric aggregates that have been written to this pipeline. + /// Index of the aggregate in the buffer. + /// Metric aggregate at the specified index. public MetricAggregate this[int index] { get @@ -82,7 +85,7 @@ public int Count } } - /// @ToDo: Complete documentation before stable release. {169}. + /// Clears the buffer. public void Clear() { this.updateLock.WaitAsync().GetAwaiter().GetResult(); @@ -96,10 +99,12 @@ public void Clear() } } - /// @ToDo: Complete documentation before stable release. {319}. - /// @ToDo: Complete documentation before stable release. {915}. - /// @ToDo: Complete documentation before stable release. {929}. - /// @ToDo: Complete documentation before stable release. {190}. + /// Stores a metric aggregate in a memory buffer. The aggregate is not further processed, + /// but it can be accessed from the buffer. If the buffer already contains items, + /// the oldest item (the one at index 0) gets discarded before adding the new item at the end of the buffer. + /// Aggregate to keep. + /// To signal cancellation of the track-operation. + /// A task representing the completion of this operation. public async Task TrackAsync(MetricAggregate metricAggregate, CancellationToken cancelToken) { Util.ValidateNotNull(metricAggregate, nameof(metricAggregate)); @@ -120,9 +125,9 @@ public async Task TrackAsync(MetricAggregate metricAggregate, CancellationToken } } - /// @ToDo: Complete documentation before stable release. {094}. - /// @ToDo: Complete documentation before stable release. {776}. - /// @ToDo: Complete documentation before stable release. {084}. + /// No-op. + /// Ignored. + /// A completed task. public Task FlushAsync(CancellationToken cancelToken) { return Task.FromResult(true); diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToApplicationInsightsPipelineConverterBase.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToApplicationInsightsPipelineConverterBase.cs index 202bddb320..02b843e55a 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToApplicationInsightsPipelineConverterBase.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToApplicationInsightsPipelineConverterBase.cs @@ -7,19 +7,23 @@ using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {533}. + /// Converts the Metrics-Aggregation-SDK exchange type for aggregates (MetricAggregate) to + /// the Application Insights exchange type for the same (MetricTelemetry). This abstract base class provides + /// common functionality between aggregates for different aggregation kinds. + /// /// @PublicExposureCandidate internal abstract class MetricAggregateToApplicationInsightsPipelineConverterBase : IMetricAggregateToTelemetryPipelineConverter { - /// @ToDo: Complete documentation before stable release. {918}. + /// Property name for storing the aggregation interval length. public const string AggregationIntervalMonikerPropertyKey = "_MS.AggregationIntervalMs"; - /// Gets @ToDo: Complete documentation before stable release. {692}.s + /// Gets the name for the aggregation kind sopported by this converter (e.g. Microsoft.Azure.Measurement). public abstract string AggregationKindMoniker { get; } - /// @ToDo: Complete documentation before stable release. {200}. - /// @ToDo: Complete documentation before stable release. {793}. - /// @ToDo: Complete documentation before stable release. {084}. + /// Converts a Microsoft.ApplicationInsights.Metrics.MetricAggregate to + /// a Microsoft.ApplicationInsights.DataContracts.MetricTelemetry. + /// A metric aggregate. + /// A metric telemetry item representing the aggregate. public object Convert(MetricAggregate aggregate) { this.ValidateAggregate(aggregate); @@ -28,9 +32,10 @@ public object Convert(MetricAggregate aggregate) return telemetryItem; } - /// @ToDo: Complete documentation before stable release. {632}. - /// @ToDo: Complete documentation before stable release. {469}. - /// @ToDo: Complete documentation before stable release. {380}. + /// Subclasses need to override this method to actually send the metric telemetry item's properties + /// based on the cntents of the aggregate and the aggregation kind. + /// A metric telemetry item representing the aggregate. + /// A metric aggregate. protected abstract void PopulateDataValues(MetricTelemetry telemetryItem, MetricAggregate aggregate); private static void PopulateTelemetryContext( diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToTelemetryPipelineConverters.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToTelemetryPipelineConverters.cs index 9c6ed3ac85..7396d9e00b 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToTelemetryPipelineConverters.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregateToTelemetryPipelineConverters.cs @@ -3,20 +3,21 @@ using System; using System.Collections.Concurrent; - /// @ToDo: Complete documentation before stable release. {573}. + /// A registry for injecting converters from MetricAggregate items to data exchange + /// types employed by the respective data ingestion/processing/sink mechanism. /// @PublicExposureCandidate internal sealed class MetricAggregateToTelemetryPipelineConverters { - /// @ToDo: Complete documentation before stable release. {097}. + /// Default singelton. public static readonly MetricAggregateToTelemetryPipelineConverters Registry = new MetricAggregateToTelemetryPipelineConverters(); private ConcurrentDictionary> pipelineTable = new ConcurrentDictionary>(); - /// @ToDo: Complete documentation before stable release. {109}. - /// @ToDo: Complete documentation before stable release. {517}. - /// @ToDo: Complete documentation before stable release. {274}. - /// @ToDo: Complete documentation before stable release. {912}. + /// Adds a converter to the registry. + /// Type of the data output pipeline. + /// Aggregation kind moniker. + /// The converter being registered. public void Add(Type pipelineType, string aggregationKindMoniker, IMetricAggregateToTelemetryPipelineConverter converter) { ValidateKeys(pipelineType, aggregationKindMoniker); @@ -29,11 +30,11 @@ public void Add(Type pipelineType, string aggregationKindMoniker, IMetricAggrega converters[aggregationKindMoniker] = converter; } - /// @ToDo: Complete documentation before stable release. {076}. - /// @ToDo: Complete documentation before stable release. {807}. - /// @ToDo: Complete documentation before stable release. {677}. - /// @ToDo: Complete documentation before stable release. {420}. - /// @ToDo: Complete documentation before stable release. {143}. + /// Attempts to get a metric aggregate converter from the registry. + /// Type of the target pipeline. + /// Aggregation kind. + /// The registered converter, or null. + /// true if a comverter was retrieved, false otherwise. public bool TryGet(Type pipelineType, string aggregationKindMoniker, out IMetricAggregateToTelemetryPipelineConverter converter) { ValidateKeys(pipelineType, aggregationKindMoniker); diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregationCycleKind.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregationCycleKind.cs index b5b399a33d..26f0e49014 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregationCycleKind.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricAggregationCycleKind.cs @@ -2,7 +2,7 @@ { using System; - /// @ToDo: Complete documentation before stable release. {447}. + /// The kind (aka purpose/target/...) of the aggregation cycle. public enum MetricAggregationCycleKind : Int32 { /// diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricConfigurationExtensions.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricConfigurationExtensions.cs index 8ba5eae262..3e30e160c0 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricConfigurationExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricConfigurationExtensions.cs @@ -14,8 +14,8 @@ public static class MetricConfigurationExtensions /// Groups constants used my metric aggregates produced by aggregators that are configured by metric configurations represented /// through instances of . See also MetricConfigurations.Common.Measurement()./>. /// - /// @ToDo: Complete documentation before stable release. {071}. - /// @ToDo: Complete documentation before stable release. {132}. + /// A specific config for a metric series with the "measurement" aggregation kind. + /// Constants for data access. public static MetricSeriesConfigurationForMeasurement.AggregateKindConstants Constants(this MetricSeriesConfigurationForMeasurement measurementConfig) { return MetricSeriesConfigurationForMeasurement.AggregateKindConstants.Instance; @@ -25,8 +25,8 @@ public static MetricSeriesConfigurationForMeasurement.AggregateKindConstants Con /// Groups constants used my metric aggregates produced by aggregators that are configured by metric configurations represented /// through instances of . See also MetricConfigurations.Common.Measurement()./>. /// - /// @ToDo: Complete documentation before stable release. {276}. - /// @ToDo: Complete documentation before stable release. {564}. + /// A specific config for a metric with the "measurement" aggregation kind. + /// Constants for data access. public static MetricSeriesConfigurationForMeasurement.AggregateKindConstants Constants(this MetricConfigurationForMeasurement measurementConfig) { return MetricSeriesConfigurationForMeasurement.AggregateKindConstants.Instance; diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricExtensions.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricExtensions.cs index dd393f84ee..ea2696b9cb 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricExtensions.cs @@ -3,7 +3,7 @@ using System; /// - /// There are some APIs on Metric that we hide from Intellisense by making them internal until the ..Extensibility namespace is imported. + /// There are some APIs on Metric that we hide from Intellisense by making them internal until the ...Extensibility namespace is imported. /// This class exposes them. /// public static class MetricExtensions @@ -11,8 +11,8 @@ public static class MetricExtensions /// /// Exposes the Configuration property for users who imported the Microsoft.ApplicationInsights.Metrics.Extensibility namespace. /// - /// @ToDo: Complete documentation before stable release. {753}. - /// @ToDo: Complete documentation before stable release. {527}. + /// A metric. + /// The configuration of the metric. public static MetricConfiguration GetConfiguration(this Metric metric) { return metric.configuration; @@ -21,8 +21,8 @@ public static MetricConfiguration GetConfiguration(this Metric metric) ///// ///// Exposes the MetricManager property for users who imported the Microsoft.ApplicationInsights.Metrics.Extensibility namespace. ///// - ///// @ToDo: Complete documentation before stable release. {615}. - ///// @ToDo: Complete documentation before stable release. {349}. + ///// The metric for which the obtain the manager. + ///// The manager of the specified metric. ////public static MetricManager GetMetricManager(this Metric metric) ////{ //// return metric._metricManager; diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricManagerExtensions.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricManagerExtensions.cs index 565b8bfbb5..7b0ee1c360 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricManagerExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricManagerExtensions.cs @@ -4,17 +4,20 @@ using System.Threading.Tasks; /// - /// There are some methods on that MetricManager needs to forward to its encapsulated MetricAggregationManager that need to be public. - /// However, in order not to pulute the API surface shown by Intellisense, we redirect them through this class, which is located in a more specialized namespace. + /// Some methods on MetricManager.AggregationManager need public access. + /// However, the property AggregationManager on MetricManager is not public, + /// nor is the type of that property (MetricAggregationManager). + /// This class exposes the necesary APIs in a specialized namespace, while avoiding polluting + /// the API surface shown by Intellisense for users who do not import the ...Extensibility namespace. /// /// @PublicExposureCandidate internal static class MetricManagerExtensions { - /// @ToDo: Complete documentation before stable release. {989}. - /// @ToDo: Complete documentation before stable release. {335}. - /// @ToDo: Complete documentation before stable release. {001}. - /// @ToDo: Complete documentation before stable release. {687}. - /// @ToDo: Complete documentation before stable release. {620}. + /// Stop the specified aggregation cycle for the specified manager and return the aggregates. + /// The metric manager. + /// The kind of the cycle to stop. + /// Timestamp that will be the end of the astopped aggregation cycle for all respective aggregators. + /// A holder that contains all the stopped aggregaors. public static AggregationPeriodSummary StopAggregators( this MetricManager metricManager, MetricAggregationCycleKind aggregationCycleKind, @@ -24,12 +27,13 @@ internal static class MetricManagerExtensions return metricManager.AggregationManager.StopAggregators(aggregationCycleKind, tactTimestamp); } - /// @ToDo: Complete documentation before stable release. {396}. - /// @ToDo: Complete documentation before stable release. {784}. - /// @ToDo: Complete documentation before stable release. {805}. - /// @ToDo: Complete documentation before stable release. {879}. - /// @ToDo: Complete documentation before stable release. {735}. - /// @ToDo: Complete documentation before stable release. {762}. + /// If the specified aggragation cycle is not active, it is started. + /// If is is already active, it is completed, and a new cycle is started. + /// The metric manager that owns the aggregation cycle. + /// The kind of the aggregation cycle to start or cycle. + /// Timestamp to b used as cycle sart for all respective aggregators. + /// Filter to be used for the new cycle. + /// A holder containing aggregates for the previous cycle, if any. public static AggregationPeriodSummary StartOrCycleAggregators( this MetricManager metricManager, MetricAggregationCycleKind aggregationCycleKind, diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesAggregatorBase.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesAggregatorBase.cs index ec666ee664..904135504e 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesAggregatorBase.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesAggregatorBase.cs @@ -5,11 +5,12 @@ using System.Threading; using System.Threading.Tasks; - using static System.FormattableString; + using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {562}. + /// The abstract base contain functionaity shared by most aggregators. + /// /// The actual type of the metric values. For most common metrics it's double. - /// However, for example a metric collecting strings to dount the number of distinct entities might have string. + /// However, for example a metric collecting strings to d-count the number of distinct entities might have string. /// @PublicExposureCandidate internal abstract class MetricSeriesAggregatorBase : IMetricSeriesAggregator { @@ -24,11 +25,11 @@ internal abstract class MetricSeriesAggregatorBase : IMetricSeri private volatile MetricValuesBufferBase metricValuesBuffer; private volatile MetricValuesBufferBase metricValuesBufferRecycle = null; - /// @ToDo: Complete documentation before stable release. {683}. - /// @ToDo: Complete documentation before stable release. {573}. - /// @ToDo: Complete documentation before stable release. {343}. - /// @ToDo: Complete documentation before stable release. {725}. - /// @ToDo: Complete documentation before stable release. {361}. + /// Initializes an aggregator instance. + /// Creates a values buffer appropriate for this aggregator. + /// Configuration of the metric series ggregated by this aggregator. + /// The metric series ggregated by this aggregator. + /// The kind of the aggregation cycle that uses this aggregator. protected MetricSeriesAggregatorBase( Func> metricValuesBufferFactory, IMetricSeriesConfiguration configuration, @@ -48,15 +49,15 @@ internal abstract class MetricSeriesAggregatorBase : IMetricSeri this.Reset(default(DateTimeOffset), default(IMetricValueFilter)); } - /// Gets @ToDo: Complete documentation before stable release. {182}. + /// Gets the data series aggregated by this aggregator. public MetricSeries DataSeries { get { return this.dataSeries; } } - /// @ToDo: Complete documentation before stable release. {895}. - /// @ToDo: Complete documentation before stable release. {406}. - /// @ToDo: Complete documentation before stable release. {839}. + /// Finishes the aggregation cycle. + /// Cycle end timestamp. + /// The aggregate summarizing the completed cycle. public MetricAggregate CompleteAggregation(DateTimeOffset periodEnd) { if (!this.isPersistent) @@ -68,8 +69,8 @@ public MetricAggregate CompleteAggregation(DateTimeOffset periodEnd) return aggregate; } - /// @ToDo: Complete documentation before stable release. {121}. - /// @ToDo: Complete documentation before stable release. {513}. + /// Clear the state of this aggregator to allow it to be reused for a new aggregation cycle. + /// start time of the new cycle. public void Reset(DateTimeOffset periodStart) { this.periodStart = periodStart; @@ -79,17 +80,17 @@ public void Reset(DateTimeOffset periodStart) this.ResetAggregate(); } - /// @ToDo: Complete documentation before stable release. {674}. - /// @ToDo: Complete documentation before stable release. {632}. - /// @ToDo: Complete documentation before stable release. {637}. + /// Clear the state of this aggregator to allow it to be reused for a new aggregation cycle. + /// Start time of the new cycle. + /// Values filter for the new cycle. public void Reset(DateTimeOffset periodStart, IMetricValueFilter valueFilter) { this.valueFilter = valueFilter; this.Reset(periodStart); } - /// @ToDo: Complete documentation before stable release. {461}. - /// @ToDo: Complete documentation before stable release. {786}. + /// Track a metric va,ue for inclusion into the current cycle. + /// The metric value. public void TrackValue(double metricValue) { if (Double.IsNaN(metricValue)) @@ -109,8 +110,8 @@ public void TrackValue(double metricValue) this.TrackFilteredConvertedValue(value); } - /// @ToDo: Complete documentation before stable release. {415}. - /// @ToDo: Complete documentation before stable release. {635}. + /// Track a metric va,ue for inclusion into the current cycle. + /// The metric value. public void TrackValue(object metricValue) { if (metricValue == null) @@ -129,8 +130,8 @@ public void TrackValue(object metricValue) this.TrackFilteredConvertedValue(value); } - /// @ToDo: Complete documentation before stable release. {040}. - /// @ToDo: Complete documentation before stable release. {556}. + /// Clear the state of this aggregator to allow it to be reused for a new aggregation cycle. + /// true if this aggregator supports reseting and was reset, or false otherwise. public bool TryRecycle() { if (this.isPersistent) @@ -142,9 +143,10 @@ public bool TryRecycle() return true; } - /// @ToDo: Complete documentation before stable release. {017}. - /// @ToDo: Complete documentation before stable release. {120}. - /// @ToDo: Complete documentation before stable release. {390}. + /// Get an aggregate of the values tracked so far without completing the ongoing aggregation cycle. + /// May not be thread-safe. + /// Timestamp to use as period end for the returned aggregate. + /// Metric aggregate containng the summary of the values tracked so far during the current cycle. public MetricAggregate CreateAggregateUnsafe(DateTimeOffset periodEnd) { this.UpdateAggregate(this.metricValuesBuffer); @@ -154,22 +156,24 @@ public MetricAggregate CreateAggregateUnsafe(DateTimeOffset periodEnd) #region Abstract Methods - /// @ToDo: Complete documentation before stable release. {238}. - /// @ToDo: Complete documentation before stable release. {096}. - /// @ToDo: Complete documentation before stable release. {421}. + /// Concrete aggregator imlemenations override this to actually create an aggregate form the tracked matric values. + /// Timestamp of the aggregation period end. + /// A new metric aggregate. protected abstract MetricAggregate CreateAggregate(DateTimeOffset periodEnd); - /// @ToDo: Complete documentation before stable release. {896}. + /// Concrete aggregator imlemenations override this to actually reset the aggregator. protected abstract void ResetAggregate(); - /// @ToDo: Complete documentation before stable release. {878}. - /// @ToDo: Complete documentation before stable release. {128}. - /// @ToDo: Complete documentation before stable release. {270}. + /// Concrete aggregator imlemenations override this to convert the metric value passed to the + /// public TrackValue(..) method to the typed used by the internal buffer. + /// Value to convert.. + /// Converted value. protected abstract TBufferedValue ConvertMetricValue(double metricValue); - /// @ToDo: Complete documentation before stable release. {928}. - /// @ToDo: Complete documentation before stable release. {941}. - /// @ToDo: Complete documentation before stable release. {657}. + /// Concrete aggregator imlemenations override this to convert the metric value passed to the + /// public TrackValue(..) method to the typed used by the internal buffer. + /// Value to convert.. + /// Converted value. protected abstract TBufferedValue ConvertMetricValue(object metricValue); /// @@ -178,10 +182,10 @@ public MetricAggregate CreateAggregateUnsafe(DateTimeOffset periodEnd) /// a lock on the metric values buffer (e.g. extracting a summary from the buffer). Stage 2 is the part of the update /// that does not need such a lock. /// - /// @ToDo: Complete documentation before stable release. {580}. - /// @ToDo: Complete documentation before stable release. {764}. - /// @ToDo: Complete documentation before stable release. {497}. - /// @ToDo: Complete documentation before stable release. {788}. + /// Interval values buffer. + /// Buffer index start to process. {764}. + /// Buffer index end to process. + /// State for passing data from stage 1 to stage 2. protected abstract object UpdateAggregate_Stage1(MetricValuesBufferBase buffer, int minFlushIndex, int maxFlushIndex); /// @@ -190,14 +194,14 @@ public MetricAggregate CreateAggregateUnsafe(DateTimeOffset periodEnd) /// a lock on the metric values buffer (e.g. extracting a summary from the buffer). Stage 2 is the part of the update /// that does not need such a lock. /// - /// @ToDo: Complete documentation before stable release. {551}. + /// State for passing data from stage 1 to stage 2. protected abstract void UpdateAggregate_Stage2(object stage1Result); #endregion Abstract Methods - /// @ToDo: Complete documentation before stable release. {203}. - /// @ToDo: Complete documentation before stable release. {570}. - /// @ToDo: Complete documentation before stable release. {632}. + /// Populate common fields of just-constructed metric aggregate. + /// Aggregate being initialized. + /// End of the cycle represented by the aggregate. protected void AddInfo_Timing_Dimensions_Context(MetricAggregate aggregate, DateTimeOffset periodEnd) { if (aggregate == null) @@ -372,7 +376,7 @@ private void TrackFilteredConvertedValue(TBufferedValue metricValue) /// /// Flushes the values buffer to update the aggregate state held by subclasses. /// - /// @ToDo: Complete documentation before stable release. {208}. + /// Buffer to process. private void UpdateAggregate(MetricValuesBufferBase buffer) { if (buffer == null) diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesExtensions.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesExtensions.cs index 1d0cf71a04..2e0a511786 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricSeriesExtensions.cs @@ -12,8 +12,8 @@ public static class MetricSeriesExtensions /// /// Exposes the Configuration property for users who imported the Microsoft.ApplicationInsights.Metrics.Extensibility namespace. /// - /// @ToDo: Complete documentation before stable release. {509}. - /// @ToDo: Complete documentation before stable release. {867}. + /// Metric data series for whcih to get configuration. + /// Configuration of the specified series. public static IMetricSeriesConfiguration GetConfiguration(this MetricSeries metricSeries) { return metricSeries.configuration; diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricValuesBuffer.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricValuesBuffer.cs index c147995a52..d1aa54ed08 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricValuesBuffer.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/MetricValuesBuffer.cs @@ -8,8 +8,12 @@ #pragma warning disable SA1649 // File name must match first type name #pragma warning disable SA1402 // File may only contain a single class - /// @ToDo: Complete documentation before stable release. {821}. - /// The tyoe of values held in the buffer. + /// The metric values buffer is the the heart of the lock-free (well, mostly) aggregation logic. + /// If allows to quickly collect metric values and to update the running aggregate at regular intervals. + /// This is required becasue aggregates for some aggregation kinds are expensive to update (e.g. some percentile + /// algorithms) and/or require a lock. By collecting a bunch of values first, the expensive/locked operation can + /// occur less frequently. + /// The type of values held in the buffer. /// @PublicExposureCandidate internal abstract class MetricValuesBufferBase { @@ -18,8 +22,6 @@ internal abstract class MetricValuesBufferBase private int lastWriteIndex = -1; private volatile int nextFlushIndex = 0; - /// @ToDo: Complete documentation before stable release. {327}. - /// @ToDo: Complete documentation before stable release. {055}. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214: Do not call overridable methods in constructors", Justification = "Call chain has been reviewed.")] public MetricValuesBufferBase(int capacity) { @@ -32,53 +34,42 @@ public MetricValuesBufferBase(int capacity) this.ResetValues(this.values); } - /// Gets @ToDo: Complete documentation before stable release. {982}. public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return this.values.Length; } } - /// Gets or sets @ToDo: Complete documentation before stable release. {934}. public int NextFlushIndex { get { return this.nextFlushIndex; } set { this.nextFlushIndex = value; } } - /// @ToDo: Complete documentation before stable release. {997}. - /// @ToDo: Complete documentation before stable release. {390}. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IncWriteIndex() { return Interlocked.Increment(ref this.lastWriteIndex); } - /// @ToDo: Complete documentation before stable release. {243}. - /// @ToDo: Complete documentation before stable release. {695}. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int PeekLastWriteIndex() { return Volatile.Read(ref this.lastWriteIndex); } - /// @ToDo: Complete documentation before stable release. {205}. - /// @ToDo: Complete documentation before stable release. {545}. - /// @ToDo: Complete documentation before stable release. {562}. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteValue(int index, TValue value) { this.WriteValueOnce(this.values, index, value); } - /// @ToDo: Complete documentation before stable release. {182}. public void ResetIndices() { this.nextFlushIndex = 0; Interlocked.Exchange(ref this.lastWriteIndex, -1); } - /// @ToDo: Complete documentation before stable release. {105}. public void ResetIndicesAndData() { Interlocked.Exchange(ref this.lastWriteIndex, this.Capacity); @@ -86,9 +77,6 @@ public void ResetIndicesAndData() this.ResetIndices(); } - /// @ToDo: Complete documentation before stable release. {628}. - /// @ToDo: Complete documentation before stable release. {939}. - /// @ToDo: Complete documentation before stable release. {285}. public TValue GetAndResetValue(int index) { TValue value = this.GetAndResetValueOnce(this.values, index); @@ -118,53 +106,33 @@ public TValue GetAndResetValue(int index) return value; } - /// @ToDo: Complete documentation before stable release. {016}. - /// . protected abstract void ResetValues(TValue[] values); - /// @ToDo: Complete documentation before stable release. {277}. - /// @ToDo: Complete documentation before stable release. {569}. - /// @ToDo: Complete documentation before stable release. {193}. - /// @ToDo: Complete documentation before stable release. {571}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract TValue GetAndResetValueOnce(TValue[] values, int index); - /// @ToDo: Complete documentation before stable release. {016}. - /// @ToDo: Complete documentation before stable release. {051}. - /// @ToDo: Complete documentation before stable release. {872}. - /// @ToDo: Complete documentation before stable release. {963}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract void WriteValueOnce(TValue[] values, int index, TValue value); - /// @ToDo: Complete documentation before stable release. {997}. - /// @ToDo: Complete documentation before stable release. {863}. - /// @ToDo: Complete documentation before stable release. {432}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract bool IsInvalidValue(TValue value); } - /// @ToDo: Complete documentation before stable release. {469}. + /// /// @PublicExposureCandidate internal sealed class MetricValuesBuffer_Double : MetricValuesBufferBase { - /// @ToDo: Complete documentation before stable release. {923}. - /// @ToDo: Complete documentation before stable release. {808}. public MetricValuesBuffer_Double(int capacity) : base(capacity) { } - /// @ToDo: Complete documentation before stable release. {611}. - /// @ToDo: Complete documentation before stable release. {523}. - /// @ToDo: Complete documentation before stable release. {416}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override bool IsInvalidValue(double value) { return Double.IsNaN(value); } - /// @ToDo: Complete documentation before stable release. {768}. - /// @ToDo: Complete documentation before stable release. {365}. protected override void ResetValues(double[] values) { for (int i = 0; i < values.Length; Interlocked.Exchange(ref values[i++], Double.NaN)) @@ -172,20 +140,12 @@ protected override void ResetValues(double[] values) } } - /// @ToDo: Complete documentation before stable release. {913}. - /// @ToDo: Complete documentation before stable release. {943}. - /// @ToDo: Complete documentation before stable release. {130}. - /// @ToDo: Complete documentation before stable release. {880}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override double GetAndResetValueOnce(double[] values, int index) { return Interlocked.Exchange(ref values[index], Double.NaN); } - /// @ToDo: Complete documentation before stable release. {684}. - /// @ToDo: Complete documentation before stable release. {246}. - /// @ToDo: Complete documentation before stable release. {452}. - /// @ToDo: Complete documentation before stable release. {035}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void WriteValueOnce(double[] values, int index, double value) { @@ -193,28 +153,21 @@ protected override void WriteValueOnce(double[] values, int index, double value) } } - /// @ToDo: Complete documentation before stable release. {201}. + /// /// @PublicExposureCandidate internal sealed class MetricValuesBuffer_Object : MetricValuesBufferBase { - /// @ToDo: Complete documentation before stable release. {314}. - /// @ToDo: Complete documentation before stable release. {683}. public MetricValuesBuffer_Object(int capacity) : base(capacity) { } - /// @ToDo: Complete documentation before stable release. {922}. - /// @ToDo: Complete documentation before stable release. {263}. - /// @ToDo: Complete documentation before stable release. {284}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override bool IsInvalidValue(object value) { return value == null; } - /// @ToDo: Complete documentation before stable release. {616}. - /// @ToDo: Complete documentation before stable release. {931}. protected override void ResetValues(object[] values) { for (int i = 0; i < values.Length; Interlocked.Exchange(ref values[i++], null)) @@ -222,20 +175,12 @@ protected override void ResetValues(object[] values) } } - /// @ToDo: Complete documentation before stable release. {940}. - /// @ToDo: Complete documentation before stable release. {673}. - /// @ToDo: Complete documentation before stable release. {249}. - /// @ToDo: Complete documentation before stable release. {311}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override object GetAndResetValueOnce(object[] values, int index) { return Interlocked.Exchange(ref values[index], null); } - /// @ToDo: Complete documentation before stable release. {003}. - /// @ToDo: Complete documentation before stable release. {110}. - /// @ToDo: Complete documentation before stable release. {970}. - /// @ToDo: Complete documentation before stable release. {000}. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void WriteValueOnce(object[] values, int index, object value) { diff --git a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/TelemetryClientExtensions.cs b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/TelemetryClientExtensions.cs index ebc103c923..07905e6672 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Extensibility/TelemetryClientExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Extensibility/TelemetryClientExtensions.cs @@ -9,15 +9,27 @@ using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {526}. + /// Metric related extension methods for the TelemetryClient. + /// Note that these APIs are in the ...Extensibility namespace and do not pollute the API surfact for users who do not import it. public static class TelemetryClientExtensions { private static ConditionalWeakTable metricManagersForTelemetryClients; - /// @ToDo: Complete documentation before stable release. {811}. - /// @ToDo: Complete documentation before stable release. {225}. - /// @ToDo: Complete documentation before stable release. {281}. - /// @ToDo: Complete documentation before stable release. {736}. + /// Gets the MetricManager for this TelemetryClient at the specified scope. + /// If a metric manager does not exist at the specified scope, it is created. + /// The telemetry client for which to get the metric manager. + /// If MetricAggregationScope.TelemetryClient is specified, + /// the metric manager specific to this client is returned. Such manager aggregates metrics for this + /// client object only. Two metrics with exactly the same id, namespace and dimensions would be aggregated + /// separately for different telemetry client objects when this scope is used.
+ /// If MetricAggregationScope.TelemetryConfiguration is specified, + /// the metric manager for the telemetry configuration of this client is returned. Such manager aggregates + /// metrics for all clients that use that telemetry configuration. Two metrics with exactly the same id, + /// namespace and dimensions would be aggregated together for different telemetry client objects that use + /// the same telemetry configuration when this scope is used.
+ /// + /// + /// The metric manager for this telemetry client at the specified scope. public static MetricManager GetMetricManager(this TelemetryClient telemetryClient, MetricAggregationScope aggregationScope) { Util.ValidateNotNull(telemetryClient, nameof(telemetryClient)); diff --git a/src/Microsoft.ApplicationInsights/Metrics/IMetricSeriesConfiguration.cs b/src/Microsoft.ApplicationInsights/Metrics/IMetricSeriesConfiguration.cs index 3ecc324305..92f2a6b0d8 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/IMetricSeriesConfiguration.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/IMetricSeriesConfiguration.cs @@ -3,16 +3,18 @@ using System; using Microsoft.ApplicationInsights.Metrics.Extensibility; - /// @ToDo: Complete documentation before stable release. {510}. + /// Abstraction for the configuration of a metric series. + /// The configuration is a factory that can produce an arbitrary aggregator to be used by the respective series. public interface IMetricSeriesConfiguration : IEquatable { - /// Gets a value indicating whether @ToDo: Complete documentation before stable release. {016}. + /// Gets a value indicating whether the aggrgator produced by this settings object is + /// persistent (carries state across aggregation periods) or not. bool RequiresPersistentAggregation { get; } - /// @ToDo: Complete documentation before stable release. {149}. - /// @ToDo: Complete documentation before stable release. {201}. - /// @ToDo: Complete documentation before stable release. {841}. - /// @ToDo: Complete documentation before stable release. {127}. + /// Create an aggregator for any series configured by this confuguration object. + /// Metric data series for which to produce the aggregator.. + /// THe kind of the aggregation cycle (not the aggregation kind). + /// A new aggregator for the specified series and the specified cycle. IMetricSeriesAggregator CreateNewAggregator(MetricSeries dataSeries, MetricAggregationCycleKind aggregationCycleKind); } } diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/GrowingCollection.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/GrowingCollection.cs index 1cec5c05d4..9d8d7d941e 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/GrowingCollection.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/GrowingCollection.cs @@ -8,7 +8,7 @@ using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {574}. + /// A very fast, lock free, unordered collection to which items can be added, but never removed. /// Type of collection elements. internal class GrowingCollection : IEnumerable { @@ -16,13 +16,13 @@ internal class GrowingCollection : IEnumerable private Segment dataHead; - /// @ToDo: Complete documentation before stable release. {371}. + /// Creates a new GrowingCollection. public GrowingCollection() { this.dataHead = new Segment(null); } - /// Gets @ToDo: Complete documentation before stable release. {072}. + /// Gets the current number of items in the collection. public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -33,8 +33,8 @@ public int Count } } - /// @ToDo: Complete documentation before stable release. {146}. - /// @ToDo: Complete documentation before stable release. {688}. + /// Adds an item to the collection. + /// Item to be added. public void Add(T item) { Segment currHead = Volatile.Read(ref this.dataHead); @@ -50,8 +50,9 @@ public void Add(T item) } } - /// @ToDo: Complete documentation before stable release. {508}. - /// @ToDo: Complete documentation before stable release. {325}. + /// Gets an enumerator over this colletion. No particular element order is guaranteed. + /// The enumerator is resilient to concurrent additions to the collection. + /// A new enumerator that will cover all items already in the collection. [MethodImpl(MethodImplOptions.AggressiveInlining)] public GrowingCollection.Enumerator GetEnumerator() { @@ -59,14 +60,18 @@ public GrowingCollection.Enumerator GetEnumerator() return enumerator; } - /// @ToDo: Complete documentation before stable release. {563}. - /// @ToDo: Complete documentation before stable release. {016}. + /// Gets an enumerator over this colletion. No particular element order is guaranteed. + /// The enumerator is resilient to concurrent additions to the collection. + /// A new enumerator that will cover all items already in the collection. [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } + /// Gets an enumerator over this colletion. No particular element order is guaranteed. + /// The enumerator is resilient to concurrent additions to the collection. + /// A new enumerator that will cover all items already in the collection. [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { @@ -75,7 +80,9 @@ IEnumerator IEnumerable.GetEnumerator() #region class Enumerator - /// @ToDo: Complete documentation before stable release. {671}. + /// An enumerator implementation for a . + /// The enumerator is resilient to concurrent additions to the collection. + /// No particular element order is guaranteed. public class Enumerator : IEnumerator { private readonly Segment head; @@ -93,7 +100,7 @@ internal Enumerator(Segment head) this.count = this.headOffset + (this.head.NextSegment == null ? 0 : this.head.NextSegment.GlobalCount); } - /// Gets @ToDo: Complete documentation before stable release. {648}. + /// Gets the total number of elements returned by this enumerator. public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -103,7 +110,7 @@ public int Count } } - /// Gets @ToDo: Complete documentation before stable release. {314}. + /// Gets the current element. public T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -122,7 +129,7 @@ object IEnumerator.Current } } - /// @ToDo: Complete documentation before stable release. {941}. + /// DIsposes this enumerator. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { @@ -130,8 +137,8 @@ public void Dispose() GC.SuppressFinalize(this); } - /// @ToDo: Complete documentation before stable release. {168}. - /// @ToDo: Complete documentation before stable release. {185}. + /// Move to the next element in the underlying colection. + /// The next element in the underlying collection. public bool MoveNext() { if (this.currentSegmentOffset == 0) @@ -154,7 +161,7 @@ public bool MoveNext() } } - /// @ToDo: Complete documentation before stable release. {307}. + /// Restarts this enumerator to the same state as it was created in. public void Reset() { this.currentSegment = this.head; diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube.cs index 320a31bc30..1dba4c4773 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube.cs @@ -168,35 +168,21 @@ internal class MultidimensionalCube private int totalPointsCount; - /// @ToDo: Complete documentation before stable release. {877}. - /// @ToDo: Complete documentation before stable release. {671}. - /// @ToDo: Complete documentation before stable release. {849}. public MultidimensionalCube(Func pointsFactory, IEnumerable subdimensionsCountLimits) : this(Int32.MaxValue, pointsFactory, subdimensionsCountLimits) { } - /// @ToDo: Complete documentation before stable release. {166}. - /// @ToDo: Complete documentation before stable release. {457}. - /// @ToDo: Complete documentation before stable release. {362}. - /// @ToDo: Complete documentation before stable release. {803}. public MultidimensionalCube(int totalPointsCountLimit, Func pointsFactory, IEnumerable subdimensionsCountLimits) : this(totalPointsCountLimit, pointsFactory, subdimensionsCountLimits?.ToArray()) { } - /// @ToDo: Complete documentation before stable release. {973}. - /// @ToDo: Complete documentation before stable release. {130}. - /// @ToDo: Complete documentation before stable release. {354}. public MultidimensionalCube(Func pointsFactory, params int[] subdimensionsCountLimits) : this(Int32.MaxValue, pointsFactory, subdimensionsCountLimits) { } - /// @ToDo: Complete documentation before stable release. {872}. - /// @ToDo: Complete documentation before stable release. {021}. - /// @ToDo: Complete documentation before stable release. {170}. - /// @ToDo: Complete documentation before stable release. {298}. public MultidimensionalCube(int totalPointsCountLimit, Func pointsFactory, params int[] subdimensionsCountLimits) { if (totalPointsCountLimit < 1) @@ -234,34 +220,26 @@ public MultidimensionalCube(int totalPointsCountLimit, FuncGets @ToDo: Complete documentation before stable release. {249}. public int DimensionsCount { get { return this.subdimensionsCountLimits.Length; } } - /// Gets @ToDo: Complete documentation before stable release. {655}. public int TotalPointsCountLimit { get { return this.totalPointsCountLimit; } } - /// Gets @ToDo: Complete documentation before stable release. {994}. public int TotalPointsCount { get { return Volatile.Read(ref this.totalPointsCount); } } - /// @ToDo: Complete documentation before stable release. {062}. - /// @ToDo: Complete documentation before stable release. {689}. - /// @ToDo: Complete documentation before stable release. {912}. public int GetSubdimensionsCountLimit(int dimension) { return this.subdimensionsCountLimits[dimension]; } - /// @ToDo: Complete documentation before stable release. {543}. - /// @ToDo: Complete documentation before stable release. {784}. public IReadOnlyCollection> GetAllPoints() { var vectors = new List>(); @@ -269,8 +247,6 @@ public int GetSubdimensionsCountLimit(int dimension) return vectors; } - /// @ToDo: Complete documentation before stable release. {251}. - /// @ToDo: Complete documentation before stable release. {164}. public void GetAllPoints(ICollection> pointContainer) { var vectors = new List>(); @@ -289,27 +265,18 @@ public void GetAllPoints(ICollection> po } } - /// @ToDo: Complete documentation before stable release. {924}. - /// @ToDo: Complete documentation before stable release. {747}. - /// @ToDo: Complete documentation before stable release. {986}. public MultidimensionalPointResult TryGetOrCreatePoint(params TDimensionValue[] coordinates) { MultidimensionalPointResult result = this.points.TryGetOrAddVector(coordinates); return result; } - /// @ToDo: Complete documentation before stable release. {438}. - /// @ToDo: Complete documentation before stable release. {321}. - /// @ToDo: Complete documentation before stable release. {686}. public MultidimensionalPointResult TryGetPoint(params TDimensionValue[] coordinates) { MultidimensionalPointResult result = this.points.TryGetVector(coordinates); return result; } - /// @ToDo: Complete documentation before stable release. {585}. - /// @ToDo: Complete documentation before stable release. {643}. - /// @ToDo: Complete documentation before stable release. {039}. public Task> TryGetOrCreatePointAsync(params TDimensionValue[] coordinates) { return this.TryGetOrCreatePointAsync( @@ -319,12 +286,6 @@ public Task> TryGetOrCreatePointAsync(params coordinates: coordinates); } - /// @ToDo: Complete documentation before stable release. {973}. - /// @ToDo: Complete documentation before stable release. {859}. - /// @ToDo: Complete documentation before stable release. {280}. - /// @ToDo: Complete documentation before stable release. {810}. - /// @ToDo: Complete documentation before stable release. {522}. - /// @ToDo: Complete documentation before stable release. {402}. public async Task> TryGetOrCreatePointAsync( TimeSpan sleepDuration, TimeSpan timeout, @@ -444,8 +405,6 @@ internal TPoint InvokePointsFactory(TDimensionValue[] coordinates) } } - /// @ToDo: Complete documentation before stable release. {211}. - /// @ToDo: Complete documentation before stable release. {567}. internal bool TryIncTotalPointsCount() { int newTotalPointsCount = Interlocked.Increment(ref this.totalPointsCount); diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube2.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube2.cs index eb94bcb482..fa795d07d2 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube2.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalCube2.cs @@ -11,8 +11,6 @@ using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {099}. - /// Type of the set over which the cube is build. For metrics, it is a metric series. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001: Types that own disposable fields should be disposable", Justification = "OK not to explicitly dispose a released SemaphoreSlim.")] internal class MultidimensionalCube2 { @@ -36,18 +34,11 @@ internal class MultidimensionalCube2 private int totalPointsCount; - /// @ToDo: Complete documentation before stable release. {016}. - /// @ToDo: Complete documentation before stable release. {334}. - /// @ToDo: Complete documentation before stable release. {636}. public MultidimensionalCube2(Func pointsFactory, params int[] dimensionValuesCountLimits) : this(Int32.MaxValue, pointsFactory, dimensionValuesCountLimits) { } - /// @ToDo: Complete documentation before stable release. {303}. - /// @ToDo: Complete documentation before stable release. {393}. - /// @ToDo: Complete documentation before stable release. {718}. - /// @ToDo: Complete documentation before stable release. {422}. public MultidimensionalCube2(int totalPointsCountLimit, Func pointsFactory, params int[] dimensionValuesCountLimits) { if (totalPointsCountLimit < 1) @@ -92,44 +83,33 @@ public MultidimensionalCube2(int totalPointsCountLimit, Func p } } - /// Gets @ToDo: Complete documentation before stable release. {993}. public int DimensionsCount { get { return this.dimensionValuesCountLimits.Length; } } - /// Gets @ToDo: Complete documentation before stable release. {319}. public int TotalPointsCountLimit { get { return this.totalPointsCountLimit; } } - /// Gets @ToDo: Complete documentation before stable release. {878}. public int TotalPointsCount { get { return Volatile.Read(ref this.totalPointsCount); } } - /// @ToDo: Complete documentation before stable release. {479}. - /// @ToDo: Complete documentation before stable release. {272}. - /// @ToDo: Complete documentation before stable release. {764}. public int GetDimensionValuesCountLimit(int dimension) { this.ValidateDimensionIndex(dimension); return this.dimensionValuesCountLimits[dimension]; } - /// @ToDo: Complete documentation before stable release. {093}. - /// @ToDo: Complete documentation before stable release. {419}. - /// @ToDo: Complete documentation before stable release. {684}. public IReadOnlyCollection GetDimensionValues(int dimension) { this.ValidateDimensionIndex(dimension); return (IReadOnlyCollection)this.dimensionValues[dimension]; } - /// @ToDo: Complete documentation before stable release. {241}. - /// @ToDo: Complete documentation before stable release. {710}. public IReadOnlyList> GetAllPoints() { List> currentPoints = new List>(this.TotalPointsCount); @@ -137,9 +117,6 @@ public IReadOnlyCollection GetDimensionValues(int dimension) return currentPoints; } - /// @ToDo: Complete documentation before stable release. {375}. - /// @ToDo: Complete documentation before stable release. {365}. - /// @ToDo: Complete documentation before stable release. {651}. public int GetAllPoints(ICollection> pointContainer) { int count = 0; @@ -154,9 +131,6 @@ public int GetAllPoints(ICollection> pointContain return count; } - /// @ToDo: Complete documentation before stable release. {444}. - /// @ToDo: Complete documentation before stable release. {769}. - /// @ToDo: Complete documentation before stable release. {229}. public MultidimensionalPointResult TryGetOrCreatePoint(params string[] coordinates) { string pointMoniker = this.GetPointMoniker(coordinates); @@ -188,18 +162,11 @@ public MultidimensionalPointResult TryGetOrCreatePoint(params string[] c } } - /// @ToDo: Complete documentation before stable release. {981}. - /// @ToDo: Complete documentation before stable release. {197}. - /// @ToDo: Complete documentation before stable release. {439}. public Task> TryGetOrCreatePointAsync(params string[] coordinates) { return this.TryGetOrCreatePointAsync(CancellationToken.None, coordinates); } - /// @ToDo: Complete documentation before stable release. {969}. - /// @ToDo: Complete documentation before stable release. {844}. - /// @ToDo: Complete documentation before stable release. {472}. - /// @ToDo: Complete documentation before stable release. {660}. public async Task> TryGetOrCreatePointAsync(CancellationToken cancelToken, params string[] coordinates) { string pointMoniker = this.GetPointMoniker(coordinates); @@ -231,9 +198,6 @@ public async Task> TryGetOrCreatePointAsync( } } - /// @ToDo: Complete documentation before stable release. {477}. - /// @ToDo: Complete documentation before stable release. {344}. - /// @ToDo: Complete documentation before stable release. {444}. public MultidimensionalPointResult TryGetPoint(params string[] coordinates) { string pointMoniker = this.GetPointMoniker(coordinates); diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalPointResult.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalPointResult.cs index 85297b8244..2d95c982e6 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalPointResult.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/ConcurrentDatastructures/MultidimensionalPointResult.cs @@ -2,8 +2,6 @@ { using System; - /// @ToDo: Complete documentation before stable release. {768}. - /// Type of the set over which the cube is build. For metics, it is a metric series. internal struct MultidimensionalPointResult { private TPoint point; @@ -24,31 +22,26 @@ internal MultidimensionalPointResult(MultidimensionalPointResultCodes successCod this.point = point; } - /// Gets @ToDo: Complete documentation before stable release. {917}. public TPoint Point { get { return this.point; } } - /// Gets @ToDo: Complete documentation before stable release. {528}. public int FailureCoordinateIndex { get { return this.failureCoordinateIndex; } } - /// Gets @ToDo: Complete documentation before stable release. {621}. public MultidimensionalPointResultCodes ResultCode { get { return this.resultCode; } } - /// Gets a value indicating whether @ToDo: Complete documentation before stable release. {353}. public bool IsPointCreatedNew { get { return (this.ResultCode & MultidimensionalPointResultCodes.Success_NewPointCreated) != 0; } } - /// Gets a value indicating whether @ToDo: Complete documentation before stable release. {268}. public bool IsSuccess { get diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/DefaultAggregationPeriodCycle.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/DefaultAggregationPeriodCycle.cs index 2d18f0ad15..b97d0d08c6 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/DefaultAggregationPeriodCycle.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/DefaultAggregationPeriodCycle.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using System.Threading.Tasks; + using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; using Microsoft.ApplicationInsights.Metrics.Extensibility; internal class DefaultAggregationPeriodCycle @@ -141,22 +142,38 @@ private static DateTimeOffset GetNextCycleTargetTime(DateTimeOffset periodStart) /// private void Run() { - while (true) + try { - DateTimeOffset now = DateTimeOffset.Now; - TimeSpan waitPeriod = GetNextCycleTargetTime(now) - now; + while (true) + { + DateTimeOffset now = DateTimeOffset.Now; + TimeSpan waitPeriod = GetNextCycleTargetTime(now) - now; + + Thread.Sleep(waitPeriod); - Thread.Sleep(waitPeriod); + int shouldBeRunning = Volatile.Read(ref this.runningState); + if (shouldBeRunning != RunningState_Running) + { + this.aggregationThread = null; + this.workerTaskCompletionControl.TrySetResult(true); + return; + } - int shouldBeRunning = Volatile.Read(ref this.runningState); - if (shouldBeRunning != RunningState_Running) + this.FetchAndTrackMetrics(); + } + } + catch (Exception ex) + { + // This is a Thread, and we don't want any exception thrown ever from this part as this would cause application crash. + try { - this.aggregationThread = null; - this.workerTaskCompletionControl.TrySetResult(true); - return; + CoreEventSource.Log.LogError(ex.ToInvariantString()); + } + catch (Exception) + { + // Intentionally empty. If EventSource writing itself is failing as well, there is nothing more to be done here. + // The best that can be done is atleast prevent application crash due to unhandledexception from Thread. } - - this.FetchAndTrackMetrics(); } } } diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregateToApplicationInsightsPipelineConverter.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregateToApplicationInsightsPipelineConverter.cs index 611aceacb8..87fbd29666 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregateToApplicationInsightsPipelineConverter.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregateToApplicationInsightsPipelineConverter.cs @@ -4,7 +4,8 @@ using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Metrics.Extensibility; - /// @ToDo: Complete documentation before stable release. {951}. + /// A specific realization of a + /// for aggregations of kind "measurement". internal class MeasurementAggregateToApplicationInsightsPipelineConverter : MetricAggregateToApplicationInsightsPipelineConverterBase { public override string AggregationKindMoniker diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregator.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregator.cs index 7ec9510ee2..331138e378 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregator.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/MeasurementAggregator.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using Microsoft.ApplicationInsights.Metrics.Extensibility; - internal sealed class MeasurementAggregator : MetricSeriesAggregatorBase + internal sealed class MeasurementAggregator : MetricSeriesAggregatorBase, IMetricSeriesAggregator { private static readonly Func> MetricValuesBufferFactory = () => new MetricValuesBuffer_Double(capacity: 500); diff --git a/src/Microsoft.ApplicationInsights/Metrics/Implementation/Util.cs b/src/Microsoft.ApplicationInsights/Metrics/Implementation/Util.cs index 06f455ed76..6fa7d17157 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/Implementation/Util.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/Implementation/Util.cs @@ -306,9 +306,9 @@ public static int CombineHashCodes(int[] arr) return hash; } - /// @ToDo: Complete documentation before stable release. {659}. - /// @ToDo: Complete documentation before stable release. {688}. - /// @ToDo: Complete documentation before stable release. {859}. + /// Copies the TelemetryContext from one object to another. + /// Copy from here. + /// Copy to here. public static void CopyTelemetryContext(TelemetryContext source, TelemetryContext target) { Util.ValidateNotNull(source, nameof(source)); diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricAggregate.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricAggregate.cs index c9fb198544..66c4c112b7 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricAggregate.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricAggregate.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Globalization; - /// @ToDo: Complete documentation before stable release. {406}. + /// Holds the metric aggregation results of a particular metric data series over an aggregation time period. + /// The specific data fields on instanced of this class are not strongly typed (property bag) which allows using this + /// aggregate type for aggregates of any aggregation kind. public class MetricAggregate { // We want to make the aggregate thread safe, but we expect no significant contention, so a simple lock will suffice. @@ -14,10 +16,10 @@ public class MetricAggregate private DateTimeOffset aggregationPeriodStart; private TimeSpan aggregationPeriodDuration; - /// @ToDo: Complete documentation before stable release. {394}. - /// @ToDo: Complete documentation before stable release. {704}. - /// @ToDo: Complete documentation before stable release. {274}. - /// @ToDo: Complete documentation before stable release. {781}. + /// Ceates a new metric aggregate. + /// The namespace of the metric that produces this aggregate. + /// The id (name) of the metric that produced this aggregate. + /// A moniker defining the kind of the aggregation used for the respective metric. public MetricAggregate(string metricNamespace, string metricId, string aggregationKindMoniker) { Util.ValidateNotNull(metricNamespace, nameof(metricNamespace)); @@ -35,16 +37,16 @@ public MetricAggregate(string metricNamespace, string metricId, string aggregati this.Data = new ConcurrentDictionary(); } - /// Gets @ToDo: Complete documentation before stable release. {747}. + /// Gets the namespace of the metric that produces this aggregate. public string MetricNamespace { get; } - /// Gets @ToDo: Complete documentation before stable release. {848}. + /// Gets the id (name) of the metric that produced this aggregate. public string MetricId { get; } - /// Gets @ToDo: Complete documentation before stable release. {959}. + /// Gets the moniker defining the kind of the aggregation used for the respective metric. public string AggregationKindMoniker { get; } - /// Gets or sets @ToDo: Complete documentation before stable release. {050}. + /// Gets or sets the start of the aggregation period summarized by this aggregate. public DateTimeOffset AggregationPeriodStart { get @@ -64,7 +66,7 @@ public DateTimeOffset AggregationPeriodStart } } - /// Gets or sets @ToDo: Complete documentation before stable release. {309}. + /// Gets or sets the length of the aggregation period summarized by this aggregate. public TimeSpan AggregationPeriodDuration { get @@ -84,10 +86,13 @@ public TimeSpan AggregationPeriodDuration } } - /// Gets @ToDo: Complete documentation before stable release. {840}. + /// Gets get table of dimension name-values that specify the data series that produced this agregate within the overall metric. public IDictionary Dimensions { get; } - /// Gets @ToDo: Complete documentation before stable release. {034}. + /// Gets the property bag that contains the actual aggregate data. + /// For example, if the aggregate was produced for a metric of the aggregation kind Measurement, + /// the look-up key for this property bag are accessible via + /// . public IDictionary Data { get; } /// @@ -97,7 +102,7 @@ public TimeSpan AggregationPeriodDuration /// Type to which to convert the object at Data[dataKey]. /// Key for the data item. /// The value to return if conversion fails. - /// @ToDo: Complete documentation before stable release. {843}. + /// This aggregate's component object available at Data[dataKey]. public T GetDataValue(string dataKey, T defaultValue) { object dataValue; diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricConfigurationForMeasurement.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricConfigurationForMeasurement.cs index ae93cce0c4..a0413bebf3 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricConfigurationForMeasurement.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricConfigurationForMeasurement.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; - /// @ToDo: Complete documentation before stable release. {218}. + /// A configuration for a metric that uses the Measurement aggregation kind. + /// A measurement contains the Min, Max, Sum and Count of the values tracked over any given + /// aggregation time period. public sealed class MetricConfigurationForMeasurement : MetricConfiguration { /// Creates a new instance of MetricConfigurationForMeasurement. diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricIdentifier.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricIdentifier.cs index 137faf0b7e..2e8d33dd06 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricIdentifier.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricIdentifier.cs @@ -2,16 +2,16 @@ { using System; using System.Collections.Generic; - using System.Globalization; using System.Runtime.CompilerServices; using System.Text; using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {085}. + /// A metric identifier encapsulates all information required to uniquely identify a metric. + /// A metric is identified by its name/id, its namespace and the names of its dimensions. public sealed class MetricIdentifier : IEquatable { - /// @ToDo: Complete documentation before stable release. {369}. + /// @Max number of dimensions supported. public const int MaxDimensionsCount = 10; private const string NoNamespaceIdentifierStringComponent = ""; @@ -25,7 +25,7 @@ public sealed class MetricIdentifier : IEquatable private static string defaultMetricNamespace = String.Empty; /// - /// Gets or sets this is what metric namespace will be set to if it is not specified. + /// Gets or sets the namespace used for metrics of no namespace was explicitly speified. /// public static string DefaultMetricNamespace { @@ -41,7 +41,7 @@ public static string DefaultMetricNamespace } } - /// @ToDo: Complete documentation before stable release. {030}. + /// Validates if a metric id / name / namespace is valid and if not, throws an ArgumentException. /// @PublicExposureCandidate private static void ValidateLiteral(string partValue, string partName, bool allowEmpty) { @@ -91,7 +91,7 @@ private static void ValidateLiteral(string partValue, string partName, bool allo private readonly int hashCode; #pragma warning restore SA1201 // Elements must appear in the correct order - /// @ToDo: Complete documentation before stable release. {150}. + /// Initializes a new metric identifier. public MetricIdentifier(string metricId) : this(metricNamespace: null, metricId: metricId, @@ -108,7 +108,7 @@ public MetricIdentifier(string metricId) { } - /// @ToDo: Complete documentation before stable release. {710}. + /// Initializes a new metric identifier. public MetricIdentifier(string metricNamespace, string metricId) : this(metricNamespace, metricId, @@ -125,7 +125,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {080}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -145,7 +145,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {219}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -166,7 +166,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {419}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -188,7 +188,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {852}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -211,7 +211,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {761}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -235,7 +235,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {629}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -260,7 +260,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {522}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -286,7 +286,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {040}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -313,7 +313,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {406}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -341,7 +341,7 @@ public MetricIdentifier(string metricNamespace, string metricId) { } - /// @ToDo: Complete documentation before stable release. {144}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -402,7 +402,7 @@ public MetricIdentifier(string metricNamespace, string metricId) this.hashCode = this.identifierString.GetHashCode(); } - /// @ToDo: Complete documentation before stable release. {694}. + /// Initializes a new metric identifier. public MetricIdentifier( string metricNamespace, string metricId, @@ -445,7 +445,7 @@ public MetricIdentifier(string metricNamespace, string metricId) /// /// Get an enumeration of the dimension names contained in this identity. The enumeration will have DimensionsCount elements. /// - /// @ToDo: Complete documentation before stable release. {589}. + /// An enumeration of the dimension names contained in this identity. public IEnumerable GetDimensionNames() { for (int d = 1; d <= this.DimensionsCount; d++) @@ -481,8 +481,8 @@ public string GetDimensionName(int dimensionNumber) } } - /// @ToDo: Complete documentation before stable release. {530}. - /// @ToDo: Complete documentation before stable release. {986}. + /// Gets a string version of this identifier. + /// A string version of this identifier. public override string ToString() { return this.identifierString; diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricManager.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricManager.cs index ab190c105e..7fc784d7b9 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricManager.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricManager.cs @@ -6,7 +6,12 @@ using System.Threading.Tasks; using Microsoft.ApplicationInsights.Metrics.Extensibility; - /// @ToDo: Complete documentation before stable release. {529}. + /// A metric manager coordinates metrics aggregation at a specific scope. + /// It keeps track of the known metrics and is ultimataly respnsibe for correctly + /// initializeing metric data time series. + /// Note that a metric manager deals with zero dimensional time series. + /// Metric objects are multidimensional collections of such series and the manager + /// merely holds a collection of such containers for its scope. public sealed class MetricManager { private readonly MetricAggregationManager aggregationManager; @@ -14,8 +19,8 @@ public sealed class MetricManager private readonly IMetricTelemetryPipeline telemetryPipeline; private readonly MetricsCollection metrics; - /// @ToDo: Complete documentation before stable release. {599}. - /// @ToDo: Complete documentation before stable release. {795}. + /// Initializes a new metric manager. + /// The destination where aggregates will be sent. public MetricManager(IMetricTelemetryPipeline telemetryPipeline) { Util.ValidateNotNull(telemetryPipeline, nameof(telemetryPipeline)); @@ -41,7 +46,7 @@ public MetricManager(IMetricTelemetryPipeline telemetryPipeline) } } - /// Gets @ToDo: Complete documentation before stable release. {328}. + /// Gets the collection of metrics available the this manager's scope. public MetricsCollection Metrics { get { return this.metrics; } @@ -57,11 +62,11 @@ internal DefaultAggregationPeriodCycle AggregationCycle get { return this.aggregationCycle; } } - /// @ToDo: Complete documentation before stable release. {362}. - /// @ToDo: Complete documentation before stable release. {953}. - /// @ToDo: Complete documentation before stable release. {176}. - /// @ToDo: Complete documentation before stable release. {016}. - /// @ToDo: Complete documentation before stable release. {996}. + /// Creates and initilizes a new metric data time series. + /// Namespace of the metric to whcih the series belongs. + /// Id (name) if the metric to which the series belongs. + /// Configuration of the series, including the aggregatio kind and other aspects. + /// A new metric data time series. public MetricSeries CreateNewSeries(string metricNamespace, string metricId, IMetricSeriesConfiguration config) { return this.CreateNewSeries( @@ -71,12 +76,12 @@ public MetricSeries CreateNewSeries(string metricNamespace, string metricId, IMe config: config); } - /// @ToDo: Complete documentation before stable release. {064}. - /// @ToDo: Complete documentation before stable release. {831}. - /// @ToDo: Complete documentation before stable release. {381}. - /// @ToDo: Complete documentation before stable release. {374}. - /// @ToDo: Complete documentation before stable release. {303}. - /// @ToDo: Complete documentation before stable release. {866}. + /// Creates and initilizes a new metric data time series. + /// Namespace of the metric to whcih the series belongs. + /// Id (name) if the metric to which the series belongs. + /// The dimension names and values of the series within its metric. + /// Configuration of the series, including the aggregatio kind and other aspects. + /// A new metric data time series. public MetricSeries CreateNewSeries( string metricNamespace, string metricId, @@ -100,11 +105,11 @@ public MetricSeries CreateNewSeries(string metricNamespace, string metricId, IMe return this.CreateNewSeries(metricIdentifier, dimensionNamesAndValues, config); } - /// @ToDo: Complete documentation before stable release. {569}. - /// @ToDo: Complete documentation before stable release. {108}. - /// @ToDo: Complete documentation before stable release. {785}. - /// @ToDo: Complete documentation before stable release. {275}. - /// @ToDo: Complete documentation before stable release. {908}. + /// Creates and initilizes a new metric data time series. + /// THe identify of the metric to whcih the series belongs. + /// The dimension names and values of the series within its metric. + /// Configuration of the series, including the aggregatio kind and other aspects. + /// A new metric data time series. public MetricSeries CreateNewSeries(MetricIdentifier metricIdentifier, IEnumerable> dimensionNamesAndValues, IMetricSeriesConfiguration config) { Util.ValidateNotNull(metricIdentifier, nameof(metricIdentifier)); @@ -114,7 +119,7 @@ public MetricSeries CreateNewSeries(MetricIdentifier metricIdentifier, IEnumerab return dataSeries; } - /// @ToDo: Complete documentation before stable release. {134}. + /// Flushes cached metric data. The default aggregation cycle will be completed/restarted if required. public void Flush() { this.Flush(flushDownstreamPipeline: true); diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricSeries.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricSeries.cs index ef0d4e4d24..34070cb3cb 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricSeries.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricSeries.cs @@ -103,20 +103,19 @@ public sealed class MetricSeries this.aggregatorCustom = null; } - /// Gets @ToDo: Complete documentation before stable release. {380}. + /// Gets a table that describes the names and values of the dimensions that describe this metric time series. public IReadOnlyDictionary DimensionNamesAndValues { get { return this.dimensionNamesAndValues; } } - /// Gets @ToDo: Complete documentation before stable release. {255}. + /// Gets the identifier of the metric that contains this metric time series. public MetricIdentifier MetricIdentifier { get; } /// - /// Tracks the specified value.
+ /// Includes the specified value into the current aggregate of this metric time series.
/// An aggregate representing tracked values will be automatically sent to the cloud ingestion endpoint at the end of each aggregation period.
- /// When non-default aggregation cycles are active, additional aggregates may be obtained by cycling respective aggregators. - /// See @ToDo to learn more about this advanced use case. + /// (Advanced note: When non-default aggregation cycles are active, additional aggregates may be obtained by cycling respective aggregators.) ///
/// The value to be aggregated. public void TrackValue(double metricValue) @@ -155,10 +154,10 @@ public void TrackValue(double metricValue) } /// - /// Tracks the specified value.
+ /// Includes the specified value into the current aggregate of this metric time series.
/// An aggregate representing tracked values will be automatically sent to the cloud ingestion endpoint at the end of each aggregation period.
- /// When non-default aggregation cycles are active, additional aggregates may be obtained by cycling respective aggregators. - /// See @ToDo to learn more about this advanced use case. + /// This overload allows creating aggregators that can aggregate non-numeric values (e.g. a distinct count of strings aggregator). + /// (Advanced note: When non-default aggregation cycles are active, additional aggregates may be obtained by cycling respective aggregators.) ///
/// The value to be aggregated. public void TrackValue(object metricValue) @@ -196,16 +195,13 @@ public void TrackValue(object metricValue) } } - /// @ToDo: Complete documentation before stable release. {218}. - /// @PublicExposureCandidate + // @PublicExposureCandidate internal void ResetAggregation() { this.ResetAggregation(periodStart: DateTimeOffset.Now); } - /// @ToDo: Complete documentation before stable release. {174}. - /// @ToDo: Complete documentation before stable release. {837}. - /// @PublicExposureCandidate + // @PublicExposureCandidate internal void ResetAggregation(DateTimeOffset periodStart) { periodStart = Util.RoundDownToSecond(periodStart); @@ -234,19 +230,13 @@ internal void ResetAggregation(DateTimeOffset periodStart) } } - /// @ToDo: Complete documentation before stable release. {036}. - /// @ToDo: Complete documentation before stable release. {909}. - /// @PublicExposureCandidate + // @PublicExposureCandidate internal MetricAggregate GetCurrentAggregateUnsafe() { return this.GetCurrentAggregateUnsafe(CycleKind.Default, DateTimeOffset.Now); } - /// @ToDo: Complete documentation before stable release. {313}. - /// @ToDo: Complete documentation before stable release. {621}. - /// @ToDo: Complete documentation before stable release. {851}. - /// @ToDo: Complete documentation before stable release. {437}. - /// @PublicExposureCandidate + // @PublicExposureCandidate internal MetricAggregate GetCurrentAggregateUnsafe(MetricAggregationCycleKind aggregationCycleKind, DateTimeOffset dateTime) { IMetricSeriesAggregator aggregator = null; @@ -470,8 +460,8 @@ private IMetricSeriesAggregator GetNewOrRecycledAggregatorInstance(MetricAggrega /// Aggregator implementations which believe that they are too expensive to recycle for this, can opt out of this strategy by returning FALSE from /// their CanRecycle property. ///
- /// @ToDo: Complete documentation before stable release. {489}. - /// @ToDo: Complete documentation before stable release. {036}. + /// The kind of the metric aggregation cycle. + /// An empty aggregator. private IMetricSeriesAggregator GetRecycledAggregatorInstance(MetricAggregationCycleKind aggregationCycleKind) { if (this.requiresPersistentAggregator) diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricSeriesConfigurationForMeasurement.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricSeriesConfigurationForMeasurement.cs index 97dcaca09e..9441fb1e7b 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricSeriesConfigurationForMeasurement.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricSeriesConfigurationForMeasurement.cs @@ -8,7 +8,9 @@ #pragma warning disable CA1034 // "Do not nest type" - part of the public API and too late to change. - /// @ToDo: Complete documentation before stable release. {023}. + /// Abstracts the configuration for a metric series aggregated using the "measurement" aggregation kind. + /// A mear=surement is best suited for metrics describing sizes or durations. + /// It contains the Min, Max, Sum and Count of values tracked during an aggregation period. public class MetricSeriesConfigurationForMeasurement : IMetricSeriesConfiguration { private readonly bool restrictToUInt32Values; @@ -22,8 +24,8 @@ static MetricSeriesConfigurationForMeasurement() new MeasurementAggregateToApplicationInsightsPipelineConverter()); } - /// @ToDo: Complete documentation before stable release. {650}. - /// @ToDo: Complete documentation before stable release. {153}. + /// CReates a new configuration. + /// Whether only integer numbers should be tracked (used for some integer-optimized backends). public MetricSeriesConfigurationForMeasurement(bool restrictToUInt32Values) { this.restrictToUInt32Values = restrictToUInt32Values; @@ -31,33 +33,36 @@ public MetricSeriesConfigurationForMeasurement(bool restrictToUInt32Values) this.hashCode = Util.CombineHashCodes(this.restrictToUInt32Values.GetHashCode()); } - /// Gets a value indicating whether @ToDo: Complete documentation before stable release. {612}. + /// Gets a value indicating whether the aggregation kind used by this configuration keeps state across aggregation cycles. + /// FOr measurements - always returns false. public bool RequiresPersistentAggregation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return false; } } - /// Gets a value indicating whether @ToDo: Complete documentation before stable release. {691}. + /// Gets a value indicating whether only integer numbers should be tracked + /// (used for some integer-optimized backends). public bool RestrictToUInt32Values { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return this.restrictToUInt32Values; } } - /// @ToDo: Complete documentation before stable release. {287}. - /// @ToDo: Complete documentation before stable release. {864}. - /// @ToDo: Complete documentation before stable release. {203}. - /// @ToDo: Complete documentation before stable release. {983}. + /// Creates a new aggregator capable of aggregating according to this configurations. + /// Metric data tie series to be aggregated. + /// The kind of th aggregtion cycle. + /// A new metric aggregator. public IMetricSeriesAggregator CreateNewAggregator(MetricSeries dataSeries, MetricAggregationCycleKind aggregationCycleKind) { IMetricSeriesAggregator aggregator = new MeasurementAggregator(this, dataSeries, aggregationCycleKind); return aggregator; } - /// @ToDo: Complete documentation before stable release. {894}. - /// @ToDo: Complete documentation before stable release. {102}. - /// @ToDo: Complete documentation before stable release. {488}. + /// Checks whether this configuration is semantically equat to a specified configuration. + /// Some objects. + /// true if the specified object is a configutation that is semantically equal to this configuration; + /// false otherwise. public override bool Equals(object obj) { if (obj != null) @@ -72,17 +77,19 @@ public override bool Equals(object obj) return false; } - /// @ToDo: Complete documentation before stable release. {278}. - /// @ToDo: Complete documentation before stable release. {067}. - /// @ToDo: Complete documentation before stable release. {117}. + /// Checks whether this configuration is semantically equat to a specified configuration. + /// Some configuration objects. + /// true if the specified object is a configutation that is semantically equal to this configuration; + /// false otherwise. public bool Equals(IMetricSeriesConfiguration other) { return this.Equals((object)other); } - /// @ToDo: Complete documentation before stable release. {078}. - /// @ToDo: Complete documentation before stable release. {374}. - /// @ToDo: Complete documentation before stable release. {070}. + /// Checks whether this configuration is semantically equat to a specified configuration. + /// Some configuration objects. + /// true if the specified object is a configutation that is semantically equal to this configuration; + /// false otherwise. public bool Equals(MetricSeriesConfigurationForMeasurement other) { if (other == null) @@ -98,8 +105,8 @@ public bool Equals(MetricSeriesConfigurationForMeasurement other) return this.RestrictToUInt32Values == other.RestrictToUInt32Values; } - /// @ToDo: Complete documentation before stable release. {869}. - /// @ToDo: Complete documentation before stable release. {755}. + /// Gets a hash code for this configuration. + /// A hash code for this configuration. public override int GetHashCode() { return this.hashCode; diff --git a/src/Microsoft.ApplicationInsights/Metrics/MetricsCollection.cs b/src/Microsoft.ApplicationInsights/Metrics/MetricsCollection.cs index d705995b63..02153013bd 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/MetricsCollection.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/MetricsCollection.cs @@ -7,36 +7,44 @@ using static System.FormattableString; - /// @ToDo: Complete documentation before stable release. {092}. + /// A collection of metrics available at a specific scope. + /// A metric is itself a colection of data time series identified by dimension name-values. public sealed class MetricsCollection : ICollection { private readonly MetricManager metricManager; private readonly ConcurrentDictionary metrics = new ConcurrentDictionary(); - /// @ToDo: Complete documentation before stable release. {109}. - /// @ToDo: Complete documentation before stable release. {758}. + /// Initializes a metric collection. + /// The manager that owns the scope of this metric collection. internal MetricsCollection(MetricManager metricManager) { Util.ValidateNotNull(metricManager, nameof(metricManager)); this.metricManager = metricManager; } - /// Gets @ToDo: Complete documentation before stable release. {304}. + /// Gets the number of metrics in this collection. public int Count { get { return this.metrics.Count; } } - /// Gets a value indicating whether @ToDo: Complete documentation before stable release. {712}. + /// Gets a value indicating whether this collection is read-only. It is not. public bool IsReadOnly { get { return false; } } - /// @ToDo: Complete documentation before stable release. {799}. - /// @ToDo: Complete documentation before stable release. {564}. - /// @ToDo: Complete documentation before stable release. {324}. - /// @ToDo: Complete documentation before stable release. {708}. + /// Gets the specified metric, or creates one if no such metric exists. + /// The identity of the metric. + /// @The configutration of the metric. + /// A metric witht he specified identify and configuration. + /// If a metric with the specified identify exists, + /// but its configuration does not match the specified configuration. + /// You may not change a metric configurations once a metric was created for the first time. + /// Either specify the same configuration every time, or specify null during every + /// invocation except the first one. null will match against any previously specified + /// configuration when retrieving existing metrics, or fall back to the default when + /// creating new metrics. public Metric GetOrCreate( MetricIdentifier metricIdentifier, MetricConfiguration metricConfiguration) @@ -63,10 +71,10 @@ public bool IsReadOnly return metric; } - /// @ToDo: Complete documentation before stable release. {799}. - /// @ToDo: Complete documentation before stable release. {564}. - /// @ToDo: Complete documentation before stable release. {324}. - /// @ToDo: Complete documentation before stable release. {708}. + /// Gets the metric with the specified identify, if it exists. + /// A metric identity. + /// The metric (if it exists) or null. + /// true if the metric was retrieved, or false otherwise. public bool TryGet(MetricIdentifier metricIdentifier, out Metric metric) { Util.ValidateNotNull(metricIdentifier, nameof(metricIdentifier)); @@ -74,15 +82,15 @@ public bool TryGet(MetricIdentifier metricIdentifier, out Metric metric) return this.metrics.TryGetValue(metricIdentifier, out metric); } - /// @ToDo: Complete documentation before stable release. {200}. + /// Removes all metrics from this collection. public void Clear() { this.metrics.Clear(); } - /// @ToDo: Complete documentation before stable release. {628}. - /// @ToDo: Complete documentation before stable release. {398}. - /// @ToDo: Complete documentation before stable release. {479}. + /// Checks if a metric is present in this collection. + /// A metric. + /// true if the metric exists in this collection, or false otherwise. public bool Contains(Metric metric) { if (metric == null) @@ -93,9 +101,9 @@ public bool Contains(Metric metric) return this.metrics.ContainsKey(metric.Identifier); } - /// @ToDo: Complete documentation before stable release. {629}. - /// @ToDo: Complete documentation before stable release. {398}. - /// @ToDo: Complete documentation before stable release. {479}. + /// Checks if a metric with th specified identity is present in this collection. + /// A metric identity. + /// true if a metric with the specified exists in this collection, or false otherwise. public bool Contains(MetricIdentifier metricIdentifier) { if (metricIdentifier == null) @@ -106,9 +114,9 @@ public bool Contains(MetricIdentifier metricIdentifier) return this.metrics.ContainsKey(metricIdentifier); } - /// @ToDo: Complete documentation before stable release. {200}. - /// @ToDo: Complete documentation before stable release. {377}. - /// @ToDo: Complete documentation before stable release. {290}. + /// Copies the contents of this collection to the specified array. + /// An artay. + /// Array index where to start the copy. public void CopyTo(Metric[] array, int arrayIndex) { Util.ValidateNotNull(array, nameof(array)); @@ -121,9 +129,9 @@ public void CopyTo(Metric[] array, int arrayIndex) this.metrics.Values.CopyTo(array, arrayIndex); } - /// @ToDo: Complete documentation before stable release. {040}. - /// @ToDo: Complete documentation before stable release. {667}. - /// @ToDo: Complete documentation before stable release. {197}. + /// Removes the specified metric from this collection. + /// A metric. + /// Whether the metric was found and removed. public bool Remove(Metric metric) { if (metric == null) @@ -135,19 +143,19 @@ public bool Remove(Metric metric) return this.metrics.TryRemove(metric.Identifier, out removedMetric); } - /// @ToDo: Complete documentation before stable release. {041}. - /// @ToDo: Complete documentation before stable release. {667}. - /// @ToDo: Complete documentation before stable release. {197}. + /// Removes a metric with the specified identity from this collection. + /// A metric identifier. + /// Whether the metric was found and removed. public bool Remove(MetricIdentifier metricIdentifier) { Metric removedMetric; return this.Remove(metricIdentifier, out removedMetric); } - /// @ToDo: Complete documentation before stable release. {041}. - /// @ToDo: Complete documentation before stable release. {667}. - /// @ToDo: Complete documentation before stable release. {668}. - /// @ToDo: Complete documentation before stable release. {197}. + /// Removes a metric with the specified identity from this collection. + /// A metric identifier. + /// The metric that was removed or null. + /// Whether the metric was found and removed. public bool Remove(MetricIdentifier metricIdentifier, out Metric removedMetric) { if (metricIdentifier == null) @@ -159,15 +167,15 @@ public bool Remove(MetricIdentifier metricIdentifier, out Metric removedMetric) return this.metrics.TryRemove(metricIdentifier, out removedMetric); } - /// @ToDo: Complete documentation before stable release. {533}. - /// @ToDo: Complete documentation before stable release. {064}. + /// Gets an enumerator for this collection. + /// An enumerator for this collection. public IEnumerator GetEnumerator() { return this.metrics.Values.GetEnumerator(); } - /// @ToDo: Complete documentation before stable release. {222}. - /// @ToDo: Complete documentation before stable release. {354}. + /// Gets an enumerator for this collection. + /// An enumerator for this collection. IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); @@ -176,7 +184,8 @@ IEnumerator IEnumerable.GetEnumerator() /// /// The Add(..) method is not supported. To add a new metric, use the GetOrCreate(..) method. /// - /// @ToDo: Complete documentation before stable release. {021}. + /// Ignored. + /// Is always thrown. void ICollection.Add(Metric unsupported) { throw new NotSupportedException(Invariant($"The Add(..) method is not supported by this {nameof(MetricsCollection)}.") diff --git a/src/Microsoft.ApplicationInsights/Metrics/TelemetryConfigurationExtensions.cs b/src/Microsoft.ApplicationInsights/Metrics/TelemetryConfigurationExtensions.cs index 8468f61dfa..ec60085f7c 100644 --- a/src/Microsoft.ApplicationInsights/Metrics/TelemetryConfigurationExtensions.cs +++ b/src/Microsoft.ApplicationInsights/Metrics/TelemetryConfigurationExtensions.cs @@ -3,12 +3,13 @@ using System; using Microsoft.ApplicationInsights.Extensibility; - /// @ToDo: Complete documentation before stable release. {737}. + /// Container for extension methods on TelemetryConfiguration. public static class TelemetryConfigurationExtensions { - /// @ToDo: Complete documentation before stable release. {923}. - /// @ToDo: Complete documentation before stable release. {456}. - /// @ToDo: Complete documentation before stable release. {580}. + /// TelemetryConfiguration.GetMetricManager(..) is a internal method to avoid puluting the public surface. + /// You can use the namespace Microsoft.ApplicationInsights.Extensibility to get access to the MetricManager via this extension method. + /// A TelemetryConfiguration. + /// The MetricManager instscne assiciated with the specified telemetry pipeline. public static MetricManager GetMetricManager(this TelemetryConfiguration telemetryPipeline) { return telemetryPipeline?.GetMetricManager(createIfNotExists: true); diff --git a/src/Microsoft.ApplicationInsights/Microsoft.ApplicationInsights.csproj b/src/Microsoft.ApplicationInsights/Microsoft.ApplicationInsights.csproj index e9362be643..6883852ecd 100644 --- a/src/Microsoft.ApplicationInsights/Microsoft.ApplicationInsights.csproj +++ b/src/Microsoft.ApplicationInsights/Microsoft.ApplicationInsights.csproj @@ -19,20 +19,24 @@ - + All - + All - + All - + All - + + + + + All @@ -41,7 +45,7 @@ All - + @@ -66,5 +70,7 @@ + + \ No newline at end of file diff --git a/src/Microsoft.ApplicationInsights/Properties/AssemblyInfo.cs b/src/Microsoft.ApplicationInsights/Properties/AssemblyInfo.cs index 9e13126bb0..4718f9384e 100644 --- a/src/Microsoft.ApplicationInsights/Properties/AssemblyInfo.cs +++ b/src/Microsoft.ApplicationInsights/Properties/AssemblyInfo.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("Microsoft.ApplicationInsights.Net45.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("Microsoft.ApplicationInsights.Net46.Tests" + AssemblyInfo.PublicKey)] diff --git a/src/Microsoft.ApplicationInsights/TelemetryClient.cs b/src/Microsoft.ApplicationInsights/TelemetryClient.cs index fb5c382ed2..5fdf86697e 100644 --- a/src/Microsoft.ApplicationInsights/TelemetryClient.cs +++ b/src/Microsoft.ApplicationInsights/TelemetryClient.cs @@ -478,24 +478,20 @@ public void InitializeInstrumentationKey(ITelemetry telemetry) [EditorBrowsable(EditorBrowsableState.Never)] public void Initialize(ITelemetry telemetry) { - string instrumentationKey = this.Context.InstrumentationKey; - - if (string.IsNullOrEmpty(instrumentationKey)) - { - instrumentationKey = this.configuration.InstrumentationKey; - } - ISupportAdvancedSampling telemetryWithSampling = telemetry as ISupportAdvancedSampling; // Telemetry can be already sampled out if that decision was made before calling Track() - bool sampledOut = telemetryWithSampling?.IsSampledOutAtHead ?? false; + bool sampledOut = false; + if (telemetryWithSampling != null) + { + sampledOut = telemetryWithSampling.ProactiveSamplingDecision == SamplingDecision.SampledOut; + } if (!sampledOut) { - var telemetryWithProperties = telemetry as ISupportProperties; - if (telemetryWithProperties != null) + if (telemetry is ISupportProperties telemetryWithProperties) { - if ((this.configuration.TelemetryChannel != null) && (this.configuration.TelemetryChannel.DeveloperMode.HasValue && this.configuration.TelemetryChannel.DeveloperMode.Value)) + if (this.configuration.TelemetryChannel?.DeveloperMode != null && this.configuration.TelemetryChannel.DeveloperMode.Value) { if (!telemetryWithProperties.Properties.ContainsKey("DeveloperMode")) { @@ -520,6 +516,13 @@ public void Initialize(ITelemetry telemetry) Utils.CopyDictionary(this.Context.GlobalProperties, telemetry.Context.GlobalProperties); } + string instrumentationKey = this.Context.InstrumentationKey; + + if (string.IsNullOrEmpty(instrumentationKey)) + { + instrumentationKey = this.configuration.InstrumentationKey; + } + telemetry.Context.Initialize(this.Context, instrumentationKey); for (int index = 0; index < this.configuration.TelemetryInitializers.Count; index++) @@ -665,7 +668,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// A Metric with the specified ID and dimensions. If you call this method several times /// with the same metric ID and dimensions for a given aggregation scope, you will receive the same /// instance of Metric. @@ -693,7 +696,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// Determines how tracked values will be aggregated.
/// Use presets in or specify your own settings. /// A Metric with the specified ID and dimensions. If you call this method several times @@ -719,7 +722,7 @@ public void Flush() /// /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// Determines how tracked values will be aggregated.
/// Use presets in or specify your own settings. /// A Metric with the specified ID and dimensions. If you call this method several times @@ -754,7 +757,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// If you previously created a metric with the same namespace, ID, dimensions /// and aggregation scope, but with a different configuration. When calling this method to get a previously @@ -782,7 +785,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// Determines how tracked values will be aggregated.
/// Use presets in or specify your own settings. @@ -810,7 +813,7 @@ public void Flush() /// /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// Determines how tracked values will be aggregated.
/// Use presets in or specify your own settings. @@ -846,7 +849,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// If you previously created a metric with the same namespace, ID, dimensions @@ -876,7 +879,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// Determines how tracked values will be aggregated.
@@ -906,7 +909,7 @@ public void Flush() /// /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// Determines how tracked values will be aggregated.
@@ -944,7 +947,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// The name of the third dimension. @@ -976,7 +979,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// The name of the third dimension. @@ -1008,7 +1011,7 @@ public void Flush() /// /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// The name of the third dimension. @@ -1048,7 +1051,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// The name of the third dimension. @@ -1082,7 +1085,7 @@ public void Flush() /// across all clients that share the same TelemetryConfiguration. /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// The name of the third dimension. @@ -1116,7 +1119,7 @@ public void Flush() /// /// The ID (name) of the metric. /// (The namespace specified in will be used. - /// To specify another namespace, user an overload that takes a MetricIdentifier parameter instead.) + /// To specify another namespace, use an overload that takes a MetricIdentifier parameter instead.) /// The name of the first dimension. /// The name of the second dimension. /// The name of the third dimension. diff --git a/src/Microsoft.ApplicationInsights/TelemetryClientExtensions.cs b/src/Microsoft.ApplicationInsights/TelemetryClientExtensions.cs index d51f2fe2ca..4e75c4622c 100644 --- a/src/Microsoft.ApplicationInsights/TelemetryClientExtensions.cs +++ b/src/Microsoft.ApplicationInsights/TelemetryClientExtensions.cs @@ -3,10 +3,11 @@ using System; using System.ComponentModel; using System.Diagnostics; - + using System.Runtime.InteropServices; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing; + using Microsoft.ApplicationInsights.Extensibility.W3C; /// /// Extension class to telemetry client that creates operation object with the respective fields initialized. @@ -14,7 +15,7 @@ [EditorBrowsable(EditorBrowsableState.Never)] public static class TelemetryClientExtensions { - private const string ChildActivityName = "Microsoft.ApplicationInsights.OperationContext"; + private const string ChildActivityName = "Microsoft.ApplicationInsights.OperationContext"; /// /// Start operation creates an operation object with a respective telemetry item. @@ -53,7 +54,33 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet if (string.IsNullOrEmpty(operationTelemetry.Context.Operation.Id) && !string.IsNullOrEmpty(operationId)) { - operationTelemetry.Context.Operation.Id = operationId; + var isActivityAvailable = ActivityExtensions.TryRun(() => + { + if (Activity.DefaultIdFormat == ActivityIdFormat.W3C) + { + if (W3CUtilities.IsCompatibleW3CTraceId(operationId)) + { + // If the user provided operationid is W3C Compatible, use it. + operationTelemetry.Context.Operation.Id = operationId; + } + else + { + // If user provided operationid is not W3C compatible, generate a new one instead. + // and store supplied value inside customproperty. + operationTelemetry.Context.Operation.Id = ActivityTraceId.CreateRandom().ToHexString(); + operationTelemetry.Properties.Add(W3CConstants.LegacyRootIdProperty, operationId); + } + } + else + { + operationTelemetry.Context.Operation.Id = operationId; + } + }); + + if (!isActivityAvailable) + { + operationTelemetry.Context.Operation.Id = operationId; + } } if (string.IsNullOrEmpty(operationTelemetry.Context.Operation.ParentId) && !string.IsNullOrEmpty(parentOperationId)) @@ -83,6 +110,9 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet throw new ArgumentNullException(nameof(operationTelemetry)); } + var telemetryContext = operationTelemetry.Context.Operation; + bool idsAssignedByUser = !string.IsNullOrEmpty(telemetryContext.Id); + // We initialize telemetry here AND in Track method because of RichPayloadEventSource. // It sends Start and Stop events for OperationTelemetry. During Start event telemetry // has to contain essential telemetry properties such as correlations ids and ikey. @@ -94,8 +124,6 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet // and does not require other properties in telemetry telemetryClient.Initialize(operationTelemetry); - var telemetryContext = operationTelemetry.Context.Operation; - // Initialize operation id if it wasn't initialized by telemetry initializers if (string.IsNullOrEmpty(operationTelemetry.Id)) { @@ -103,17 +131,14 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet } // If the operation is not executing in the context of any other operation - // set its name and id as a context (root) operation name and id - if (string.IsNullOrEmpty(telemetryContext.Id)) - { - telemetryContext.Id = operationTelemetry.Id; - } - + // set its name as a context (root) operation name. if (string.IsNullOrEmpty(telemetryContext.Name)) { telemetryContext.Name = operationTelemetry.Name; } + OperationHolder operationHolder = null; + var isActivityAvailable = ActivityExtensions.TryRun(() => { var parentActivity = Activity.Current; @@ -130,21 +155,64 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet operationActivity.SetOperationName(operationName); } - if (parentActivity == null) + if (idsAssignedByUser) { - // telemetryContext.Id is always set: if it was null, it is set to opTelemetry.Id and opTelemetry.Id is never null - operationActivity.SetParentId(telemetryContext.Id); + if (Activity.DefaultIdFormat == ActivityIdFormat.W3C) + { + if (W3CUtilities.IsCompatibleW3CTraceId(telemetryContext.Id)) + { + // If the user provided operationId is W3C Compatible, use it. + operationActivity.SetParentId(ActivityTraceId.CreateFromString(telemetryContext.Id.AsSpan()), + default(ActivitySpanId), ActivityTraceFlags.None); + } + else + { + // If user provided operationId is not W3C compatible, generate a new one instead. + // and store supplied value inside custom property. + operationTelemetry.Properties.Add(W3CConstants.LegacyRootIdProperty, telemetryContext.Id); + telemetryContext.Id = null; + } + } + else + { + operationActivity.SetParentId(telemetryContext.Id); + } } operationActivity.Start(); - operationTelemetry.Id = operationActivity.Id; + + if (operationActivity.IdFormat == ActivityIdFormat.W3C) + { + if (string.IsNullOrEmpty(telemetryContext.Id)) + { + telemetryContext.Id = operationActivity.TraceId.ToHexString(); + } + + // ID takes the form |TraceID.SpanId. + // TelemetryContext.Id used instead of TraceID.ToHexString() for perf. + operationTelemetry.Id = W3CUtilities.FormatTelemetryId(telemetryContext.Id, operationActivity.SpanId.ToHexString()); + } + else + { + if (string.IsNullOrEmpty(telemetryContext.Id)) + { + telemetryContext.Id = operationActivity.RootId; + } + + operationTelemetry.Id = operationActivity.Id; + } + + operationHolder = new OperationHolder(telemetryClient, operationTelemetry, parentActivity == operationActivity.Parent ? null : parentActivity); }); - var operationHolder = new OperationHolder(telemetryClient, operationTelemetry); if (!isActivityAvailable) { // Parent context store is assigned to operation that is used to restore call context. - operationHolder.ParentContext = CallContextHelpers.GetCurrentOperationContext(); + operationHolder = new OperationHolder(telemetryClient, operationTelemetry) + { + ParentContext = CallContextHelpers.GetCurrentOperationContext(), + }; + telemetryContext.Id = operationTelemetry.Id; } operationTelemetry.Start(); @@ -229,6 +297,14 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet throw new ArgumentNullException(nameof(activity)); } + Activity originalActivity = null; + + // not started activity, default case + if (activity.Id == null) + { + originalActivity = Activity.Current; + } + activity.Start(); T operationTelemetry = ActivityToTelemetry(activity); @@ -245,7 +321,7 @@ public static IOperationHolder StartOperation(this TelemetryClient telemet operationTelemetry.Start(); - return new OperationHolder(telemetryClient, operationTelemetry); + return new OperationHolder(telemetryClient, operationTelemetry, originalActivity); } private static T ActivityToTelemetry(Activity activity) where T : OperationTelemetry, new() @@ -255,10 +331,18 @@ private static T ActivityToTelemetry(Activity activity) where T : OperationTe var telemetry = new T { Name = activity.OperationName }; OperationContext operationContext = telemetry.Context.Operation; - operationContext.Name = activity.GetOperationName(); - operationContext.Id = activity.RootId; + operationContext.Name = activity.GetOperationName(); operationContext.ParentId = activity.ParentId; - telemetry.Id = activity.Id; + if (activity.IdFormat == ActivityIdFormat.W3C) + { + operationContext.Id = activity.TraceId.ToHexString(); + telemetry.Id = W3CUtilities.FormatTelemetryId(operationContext.Id, activity.SpanId.ToHexString()); + } + else + { + operationContext.Id = activity.RootId; + telemetry.Id = activity.Id; + } foreach (var item in activity.Baggage) { diff --git a/src/ServerTelemetryChannel/AdaptiveSamplingTelemetryProcessor.cs b/src/ServerTelemetryChannel/AdaptiveSamplingTelemetryProcessor.cs index 599b3f1ab8..affba3c5d9 100644 --- a/src/ServerTelemetryChannel/AdaptiveSamplingTelemetryProcessor.cs +++ b/src/ServerTelemetryChannel/AdaptiveSamplingTelemetryProcessor.cs @@ -4,7 +4,6 @@ using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; - using Microsoft.ApplicationInsights.WindowsServer.Channel.Implementation; using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.Implementation; /// @@ -60,6 +59,7 @@ public AdaptiveSamplingTelemetryProcessor(ITelemetryProcessor next) this.samplingProcessor = new SamplingTelemetryProcessor(next, this.estimatorProcessor) { SamplingPercentage = this.estimatorSettings.InitialSamplingPercentage, + ProactiveSamplingPercentage = null, }; } @@ -280,6 +280,7 @@ protected virtual void Dispose(bool disposing) if (isSamplingPercentageChanged) { this.samplingProcessor.SamplingPercentage = newSamplingPercentage; + this.samplingProcessor.ProactiveSamplingPercentage = 100 / this.estimatorProcessor.CurrentProactiveSamplingRate; TelemetryChannelEventSource.Log.SamplingChanged(newSamplingPercentage); } diff --git a/src/ServerTelemetryChannel/AssemblyInfo.cs b/src/ServerTelemetryChannel/AssemblyInfo.cs index 27f7ecffaf..e99f29a436 100644 --- a/src/ServerTelemetryChannel/AssemblyInfo.cs +++ b/src/ServerTelemetryChannel/AssemblyInfo.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -[assembly: CLSCompliant(true)] [assembly: InternalsVisibleTo("Microsoft.ApplicationInsights.Net45.Tests" + AssemblyInfo.PublicKey)] [assembly: InternalsVisibleTo("Microsoft.ApplicationInsights.Net46.Tests" + AssemblyInfo.PublicKey)] diff --git a/src/ServerTelemetryChannel/Implementation/NetworkAvailabilityTransmissionPolicy.cs b/src/ServerTelemetryChannel/Implementation/NetworkAvailabilityTransmissionPolicy.cs index e1ec7a2955..e7106b99a0 100644 --- a/src/ServerTelemetryChannel/Implementation/NetworkAvailabilityTransmissionPolicy.cs +++ b/src/ServerTelemetryChannel/Implementation/NetworkAvailabilityTransmissionPolicy.cs @@ -98,6 +98,10 @@ private bool IsNetworkAvailable() { TelemetryChannelEventSource.Log.SubscribeToNetworkFailureWarning(nie.ToString()); } + catch (Exception ex) + { + TelemetryChannelEventSource.Log.SubscribeToNetworkFailureWarning(ex.ToString()); + } return result; } diff --git a/src/ServerTelemetryChannel/Implementation/SamplingInternals/SamplingIncludesUtility.cs b/src/ServerTelemetryChannel/Implementation/SamplingInternals/SamplingIncludesUtility.cs new file mode 100644 index 0000000000..3c1d46aff2 --- /dev/null +++ b/src/ServerTelemetryChannel/Implementation/SamplingInternals/SamplingIncludesUtility.cs @@ -0,0 +1,81 @@ +namespace Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.Implementation.SamplingInternals +{ + using System; + using System.Collections.Generic; + + using Microsoft.ApplicationInsights.DataContracts; + + /// + /// This utility will calculate the IncludedTypes bitmask based on the delimited input string. + /// + internal static class SamplingIncludesUtility + { + private const string DependencyTelemetryName = "DEPENDENCY"; + private const string EventTelemetryName = "EVENT"; + private const string ExceptionTelemetryName = "EXCEPTION"; + private const string PageViewTelemetryName = "PAGEVIEW"; + private const string RequestTelemetryName = "REQUEST"; + private const string TraceTelemetryName = "TRACE"; + + private static readonly char[] ListSeparators = { ';' }; + + /// + /// Calculate an Included Bitmask based on the input string. + /// Starts with Enum.None value and adds types. + /// + /// Delimited string of types to be included. + /// Bitmask representing types to include. + public static SamplingTelemetryItemTypes CalculateFromIncludes(string includesString) + { + return Calculate(operation: IncludeOperator, flags: SamplingTelemetryItemTypes.None, input: includesString); + } + + /// + /// Calculate an Included Bitmask based on the input string. + /// Starts with Enum.ALL (~None) and removes types. + /// + /// Delimited string of types to be excluded. + /// Bitmask representing types to include. + public static SamplingTelemetryItemTypes CalculateFromExcludes(string excludesString) + { + return Calculate(operation: ExcludeOperator, flags: ~SamplingTelemetryItemTypes.None, input: excludesString); + } + + private static IDictionary GetAllowedTypes() + { + return new Dictionary(6, StringComparer.OrdinalIgnoreCase) + { + { DependencyTelemetryName, SamplingTelemetryItemTypes.RemoteDependency }, // DependencyTelemetry + { EventTelemetryName, SamplingTelemetryItemTypes.Event }, // EventTelemetry + { ExceptionTelemetryName, SamplingTelemetryItemTypes.Exception }, // ExceptionTelemetry + { PageViewTelemetryName, SamplingTelemetryItemTypes.PageView }, // PageViewTelemetry + { RequestTelemetryName, SamplingTelemetryItemTypes.Request }, // RequestTelemetry + { TraceTelemetryName, SamplingTelemetryItemTypes.Message }, // TraceTelemetry + }; + } + + private static SamplingTelemetryItemTypes Calculate(Func operation, SamplingTelemetryItemTypes flags, string input) + { + if (!string.IsNullOrEmpty(input)) + { + var allowedTypes = GetAllowedTypes(); + + foreach (string item in SplitInput(input)) + { + if (allowedTypes.TryGetValue(item, out SamplingTelemetryItemTypes value)) + { + flags = operation(flags, value); + } + } + } + + return flags; + } + + private static string[] SplitInput(string input) => input.Split(ListSeparators, StringSplitOptions.RemoveEmptyEntries); + + private static SamplingTelemetryItemTypes IncludeOperator(SamplingTelemetryItemTypes flags, SamplingTelemetryItemTypes value) => flags |= value; + + private static SamplingTelemetryItemTypes ExcludeOperator(SamplingTelemetryItemTypes flags, SamplingTelemetryItemTypes value) => flags &= ~value; + } +} diff --git a/src/ServerTelemetryChannel/Implementation/SamplingPercentageEstimatorTelemetryProcessor.cs b/src/ServerTelemetryChannel/Implementation/SamplingPercentageEstimatorTelemetryProcessor.cs index 8fbe23c672..d9fa79562e 100644 --- a/src/ServerTelemetryChannel/Implementation/SamplingPercentageEstimatorTelemetryProcessor.cs +++ b/src/ServerTelemetryChannel/Implementation/SamplingPercentageEstimatorTelemetryProcessor.cs @@ -4,8 +4,8 @@ using System.Threading; using Microsoft.ApplicationInsights.Channel; + using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Extensibility; - using Microsoft.ApplicationInsights.WindowsServer.Channel.Implementation; /// /// Represents a method that is invoked every time sampling percentage is evaluated @@ -44,6 +44,11 @@ internal class SamplingPercentageEstimatorTelemetryProcessor : ITelemetryProcess /// private ExponentialMovingAverageCounter itemCount; + /// + /// Average proactively SampledIn telemetry item counter. + /// + private ExponentialMovingAverageCounter proactivelySampledInCount; + /// /// Evaluation timer. /// @@ -54,11 +59,6 @@ internal class SamplingPercentageEstimatorTelemetryProcessor : ITelemetryProcess /// private TimeSpan evaluationInterval; - /// - /// Current sampling rate. - /// - private int currenSamplingRate; - /// /// Last date and time sampling percentage was changed. /// @@ -89,25 +89,17 @@ public SamplingPercentageEstimatorTelemetryProcessor(ITelemetryProcessor next) Channel.Implementation.AdaptiveSamplingPercentageEvaluatedCallback callback, ITelemetryProcessor next) { - if (settings == null) - { - throw new ArgumentNullException(nameof(settings)); - } - - if (next == null) - { - throw new ArgumentNullException(nameof(next)); - } - this.evaluationCallback = callback; - this.settings = settings; - this.next = next; + this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); + this.next = next ?? throw new ArgumentNullException(nameof(next)); - this.currenSamplingRate = settings.EffectiveInitialSamplingRate; + this.CurrentSamplingRate = settings.EffectiveInitialSamplingRate; + this.CurrentProactiveSamplingRate = settings.EffectiveInitialSamplingRate; this.itemCount = new ExponentialMovingAverageCounter(settings.EffectiveMovingAverageRatio); + this.proactivelySampledInCount = new ExponentialMovingAverageCounter(settings.EffectiveMovingAverageRatio); - this.samplingPercentageLastChangeDateTime = DateTimeOffset.UtcNow; + this.samplingPercentageLastChangeDateTime = PreciseTimestamp.GetUtcNow(); // set evaluation interval to default value if it is negative or zero this.evaluationInterval = this.settings.EffectiveEvaluationInterval; @@ -123,11 +115,12 @@ public SamplingPercentageEstimatorTelemetryProcessor(ITelemetryProcessor next) /// /// Gets or sets current sampling rate. /// - internal int CurrentSamplingRate - { - get => this.currenSamplingRate; - set => this.currenSamplingRate = value; - } + internal int CurrentSamplingRate { get; set; } + + /// + /// Gets current proactive sampling rate sampling rate. + /// + internal double CurrentProactiveSamplingRate { get; private set; } /// /// Processes telemetry item. @@ -138,6 +131,12 @@ public void Process(ITelemetry item) // increment post-sampling telemetry item counter this.itemCount.Increment(); + if (item is ISupportAdvancedSampling advancedSamplingItem && + advancedSamplingItem.ProactiveSamplingDecision == SamplingDecision.SampledIn) + { + this.proactivelySampledInCount.Increment(); + } + // continue processing telemetry item with the next telemetry processor this.next.Process(item); } @@ -182,8 +181,14 @@ private void EstimateSamplingPercentage(object state) // get observed after-sampling eps double observedEps = this.itemCount.StartNewInterval() / this.evaluationInterval.TotalSeconds; + // get observed after-sampling eps + double observedProactiveEps = this.proactivelySampledInCount.StartNewInterval() / this.evaluationInterval.TotalSeconds; + + // we see events post sampling, so get pre-sampling eps + double beforeSamplingEps = observedEps * this.CurrentSamplingRate; + // we see events post sampling, so get pre-sampling eps - double beforeSamplingEps = observedEps * this.currenSamplingRate; + double beforeProactiveSamplingEps = observedProactiveEps * this.CurrentProactiveSamplingRate; // calculate suggested sampling rate int suggestedSamplingRate = (int)Math.Ceiling(beforeSamplingEps / this.settings.EffectiveMaxTelemetryItemsPerSecond); @@ -199,6 +204,13 @@ private void EstimateSamplingPercentage(object state) suggestedSamplingRate = this.settings.EffectiveMinSamplingRate; } + double suggestedProactiveSamplingRate = beforeProactiveSamplingEps / this.settings.EffectiveMaxTelemetryItemsPerSecond; + + if (suggestedProactiveSamplingRate < this.settings.EffectiveMinSamplingRate) + { + suggestedProactiveSamplingRate = this.settings.EffectiveMinSamplingRate; + } + // see if evaluation interval was changed and apply change if (this.evaluationInterval != this.settings.EffectiveEvaluationInterval) { @@ -207,13 +219,13 @@ private void EstimateSamplingPercentage(object state) } // check to see if sampling rate needs changes - bool samplingPercentageChangeNeeded = suggestedSamplingRate != this.currenSamplingRate; + bool samplingPercentageChangeNeeded = suggestedSamplingRate != this.CurrentSamplingRate; if (samplingPercentageChangeNeeded) { // check to see if enough time passed since last sampling % change - if ((DateTimeOffset.UtcNow - this.samplingPercentageLastChangeDateTime) < - (suggestedSamplingRate > this.currenSamplingRate + if ((PreciseTimestamp.GetUtcNow() - this.samplingPercentageLastChangeDateTime) < + (suggestedSamplingRate > this.CurrentSamplingRate ? this.settings.EffectiveSamplingPercentageDecreaseTimeout : this.settings.EffectiveSamplingPercentageIncreaseTimeout)) { @@ -230,7 +242,7 @@ private void EstimateSamplingPercentage(object state) { this.evaluationCallback( observedEps, - 100.0 / this.currenSamplingRate, + 100.0 / this.CurrentSamplingRate, 100.0 / suggestedSamplingRate, samplingPercentageChangeNeeded, this.settings); @@ -244,8 +256,9 @@ private void EstimateSamplingPercentage(object state) if (samplingPercentageChangeNeeded) { // apply sampling percentage change - this.samplingPercentageLastChangeDateTime = DateTimeOffset.UtcNow; - this.currenSamplingRate = suggestedSamplingRate; + this.samplingPercentageLastChangeDateTime = PreciseTimestamp.GetUtcNow(); + this.CurrentSamplingRate = suggestedSamplingRate; + this.CurrentProactiveSamplingRate = suggestedProactiveSamplingRate; } if (samplingPercentageChangeNeeded || @@ -254,6 +267,8 @@ private void EstimateSamplingPercentage(object state) // since we're observing event count post sampling and we're about // to change sampling rate or change coefficient, reset counter this.itemCount = new ExponentialMovingAverageCounter(this.settings.EffectiveMovingAverageRatio); + this.proactivelySampledInCount = + new ExponentialMovingAverageCounter(this.settings.EffectiveMovingAverageRatio); } } } diff --git a/src/ServerTelemetryChannel/Implementation/TelemetryChannelEventSource.cs b/src/ServerTelemetryChannel/Implementation/TelemetryChannelEventSource.cs index 08a3a8d3e7..237e007e08 100644 --- a/src/ServerTelemetryChannel/Implementation/TelemetryChannelEventSource.cs +++ b/src/ServerTelemetryChannel/Implementation/TelemetryChannelEventSource.cs @@ -526,6 +526,12 @@ public void ItemProactivelySampledOut(string telemetryType, string appDomainName this.WriteEvent(72, telemetryType ?? string.Empty, this.ApplicationName); } + [Event(73, Message = "Configuration Error: Cannot specify both Included and Excluded types in the sampling processor. Included will be ignored.", Level = EventLevel.Warning)] + public void SamplingConfigErrorBothTypes(string appDomainName = "Incorrect") + { + this.WriteEvent(73, this.ApplicationName); + } + private static string GetApplicationName() { //// We want to add application name to all events BUT diff --git a/src/ServerTelemetryChannel/Implementation/TransmissionSender.cs b/src/ServerTelemetryChannel/Implementation/TransmissionSender.cs index 415d333051..21e076167a 100644 --- a/src/ServerTelemetryChannel/Implementation/TransmissionSender.cs +++ b/src/ServerTelemetryChannel/Implementation/TransmissionSender.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.ApplicationInsights.Channel; + using Microsoft.ApplicationInsights.Common.Extensions; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; @@ -165,7 +166,7 @@ private async Task StartSending(Transmission transmission) int currentCapacity = Interlocked.Decrement(ref this.transmissionCount); if (exception != null) { - TelemetryChannelEventSource.Log.TransmissionSendingFailedWarning(acceptedTransmission.Id, exception.ToString()); + TelemetryChannelEventSource.Log.TransmissionSendingFailedWarning(acceptedTransmission.Id, exception.ToLogString()); } else { diff --git a/src/ServerTelemetryChannel/PreciseTimestamp.cs b/src/ServerTelemetryChannel/PreciseTimestamp.cs new file mode 100644 index 0000000000..b679ac99a3 --- /dev/null +++ b/src/ServerTelemetryChannel/PreciseTimestamp.cs @@ -0,0 +1,94 @@ +namespace Microsoft.ApplicationInsights +{ + using System; + using System.Diagnostics; +#if NET45 || NET46 + using System.Diagnostics.CodeAnalysis; + using System.Threading; +#endif + + internal class PreciseTimestamp + { + /// + /// Multiplier to convert Stopwatch ticks to TimeSpan ticks. + /// + internal static readonly double StopwatchTicksToTimeSpanTicks = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency; + +#if NET45 || NET46 + private static readonly Timer SyncTimeUpdater; + private static TimeSync timeSync = new TimeSync(); + + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Enforcing static fields initialization.")] + static PreciseTimestamp() + { + SyncTimeUpdater = InitializeSyncTimer(); + } +#endif + + /// + /// Returns high resolution (1 DateTime tick) current UTC DateTime. + /// + public static DateTimeOffset GetUtcNow() + { +#if NET45 || NET46 + // DateTime.UtcNow accuracy on .NET Framework is ~16ms, this method + // uses combination of Stopwatch and DateTime to calculate accurate UtcNow. + + var tmp = timeSync; + + // Timer ticks need to be converted to DateTime ticks + long dateTimeTicksDiff = (long)((Stopwatch.GetTimestamp() - tmp.SyncStopwatchTicks) * StopwatchTicksToTimeSpanTicks); + + // DateTime.AddSeconds (or Milliseconds) rounds value to 1 ms, use AddTicks to prevent it + return tmp.SyncUtcNow.AddTicks(dateTimeTicksDiff); +#else + return DateTimeOffset.UtcNow; +#endif + } + +#if NET45 || NET46 + private static void Sync() + { + // wait for DateTime.UtcNow update to the next granular value + Thread.Sleep(1); + timeSync = new TimeSync(); + } + + private static Timer InitializeSyncTimer() + { + Timer timer; + // Don't capture the current ExecutionContext and its AsyncLocals onto the timer causing them to live forever + bool restoreFlow = false; + try + { + if (!ExecutionContext.IsFlowSuppressed()) + { + ExecutionContext.SuppressFlow(); + restoreFlow = true; + } + + // fire timer every 2 hours, Stopwatch is not very precise over long periods of time, + // so we need to correct it from time to time + // https://docs.microsoft.com/en-us/windows/desktop/SysInfo/acquiring-high-resolution-time-stamps + timer = new Timer(s => { Sync(); }, null, 0, 7200000); + } + finally + { + // Restore the current ExecutionContext + if (restoreFlow) + { + ExecutionContext.RestoreFlow(); + } + } + + return timer; + } + + private class TimeSync + { + public readonly DateTimeOffset SyncUtcNow = DateTimeOffset.UtcNow; + public readonly long SyncStopwatchTicks = Stopwatch.GetTimestamp(); + } +#endif + } +} diff --git a/src/ServerTelemetryChannel/SamplingTelemetryProcessor.cs b/src/ServerTelemetryChannel/SamplingTelemetryProcessor.cs index 80be4fb0d5..2a2d1ae7ee 100644 --- a/src/ServerTelemetryChannel/SamplingTelemetryProcessor.cs +++ b/src/ServerTelemetryChannel/SamplingTelemetryProcessor.cs @@ -1,14 +1,13 @@ namespace Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel { using System; - using System.Collections.Generic; - using System.Threading; using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.Implementation; + using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.Implementation.SamplingInternals; /// /// Represents a telemetry processor for sampling telemetry at a fixed-rate before sending to Application Insights. @@ -16,19 +15,8 @@ /// public sealed class SamplingTelemetryProcessor : ITelemetryProcessor { - private const string DependencyTelemetryName = "DEPENDENCY"; - private const string EventTelemetryName = "EVENT"; - private const string ExceptionTelemetryName = "EXCEPTION"; - private const string PageViewTelemetryName = "PAGEVIEW"; - private const string RequestTelemetryName = "REQUEST"; - private const string TraceTelemetryName = "TRACE"; - - private readonly char[] listSeparators = { ';' }; - private readonly IDictionary allowedTypes; - private readonly AtomicSampledItemsCounter proactivelySampledOutCounters = new AtomicSampledItemsCounter(); - private SamplingTelemetryItemTypes excludedTypesFlags; private string excludedTypesString; private SamplingTelemetryItemTypes includedTypesFlags; @@ -40,24 +28,10 @@ public sealed class SamplingTelemetryProcessor : ITelemetryProcessor /// public SamplingTelemetryProcessor(ITelemetryProcessor next) { - if (next == null) - { - throw new ArgumentNullException(nameof(next)); - } - this.SamplingPercentage = 100.0; - this.SampledNext = next; + this.ProactiveSamplingPercentage = null; + this.SampledNext = next ?? throw new ArgumentNullException(nameof(next)); this.UnsampledNext = next; - - this.allowedTypes = new Dictionary(6, StringComparer.OrdinalIgnoreCase) - { - { DependencyTelemetryName, typeof(DependencyTelemetry) }, - { EventTelemetryName, typeof(EventTelemetry) }, - { ExceptionTelemetryName, typeof(ExceptionTelemetry) }, - { PageViewTelemetryName, typeof(PageViewTelemetry) }, - { RequestTelemetryName, typeof(RequestTelemetry) }, - { TraceTelemetryName, typeof(TraceTelemetry) }, - }; } internal SamplingTelemetryProcessor(ITelemetryProcessor unsampledNext, ITelemetryProcessor sampledNext) : this(sampledNext) @@ -69,6 +43,7 @@ internal SamplingTelemetryProcessor(ITelemetryProcessor unsampledNext, ITelemetr /// Gets or sets a semicolon separated list of telemetry types that should not be sampled. /// Allowed type names: Dependency, Event, Exception, PageView, Request, Trace. /// Types listed are excluded even if they are set in IncludedTypes. + /// Do not set both ExcludedTypes and IncludedTypes. ExcludedTypes will take precedence over IncludedTypes. /// public string ExcludedTypes { @@ -80,14 +55,27 @@ public string ExcludedTypes set { this.excludedTypesString = value; - this.excludedTypesFlags = this.PopulateSamplingFlagsFromTheInput(value); + + if (value != null) + { + var newIncludesFlags = SamplingIncludesUtility.CalculateFromExcludes(value); + this.includedTypesFlags = newIncludesFlags; + + if (this.includedTypesString != null) + { + // excluded will always overwrite included. Log a "Configuration Error". + TelemetryChannelEventSource.Log.SamplingConfigErrorBothTypes(); + } + } } } /// /// Gets or sets a semicolon separated list of telemetry types that should be sampled. + /// Allowed type names: Dependency, Event, Exception, PageView, Request, Trace. /// If left empty all types are included implicitly. /// Types are not included if they are set in ExcludedTypes. + /// Do not set both ExcludedTypes and IncludedTypes. ExcludedTypes will take precedence over IncludedTypes. /// public string IncludedTypes { @@ -99,7 +87,20 @@ public string IncludedTypes set { this.includedTypesString = value; - this.includedTypesFlags = this.PopulateSamplingFlagsFromTheInput(value); + + if (value != null) + { + if (this.excludedTypesString != null) + { + // included cannot overwrite excluded. Log a "Configuration Error". + TelemetryChannelEventSource.Log.SamplingConfigErrorBothTypes(); + } + else + { + var newIncludesFlags = SamplingIncludesUtility.CalculateFromIncludes(value); + this.includedTypesFlags = newIncludesFlags; + } + } } } @@ -113,6 +114,11 @@ public string IncludedTypes /// public double SamplingPercentage { get; set; } + /// + /// Gets or sets current proactive-sampling percentage of telemetry items. + /// + internal double? ProactiveSamplingPercentage { get; set; } + /// /// Gets or sets the next TelemetryProcessor in call chain to send evaluated (sampled) telemetry items to. /// @@ -177,7 +183,26 @@ public void Process(ITelemetry item) //// Ok, now we can actually sample: samplingSupportingTelemetry.SamplingPercentage = samplingPercentage; - bool isSampledIn = SamplingScoreGenerator.GetSamplingScore(item) < samplingPercentage; + + bool isSampledIn; + + // if this is executed in adaptive sampling processor (rate ratio has value), + // and item supports proactive sampling and was sampled in before, we'll give it more weight + if (this.ProactiveSamplingPercentage.HasValue && + advancedSamplingSupportingTelemetry != null && + advancedSamplingSupportingTelemetry.ProactiveSamplingDecision == SamplingDecision.SampledIn) + { + // if current rate of proactively sampled-in telemetry is too high, ProactiveSamplingPercentage is low: + // we'll sample in as much proactively sampled in items as we can (based on their sampling score) + // so that we still keep target rate. + // if current rate of proactively sampled-in telemetry is less that configured, ProactiveSamplingPercentage + // is high - it could be > 100 - and we'll sample in all items with proactive SampledIn decision (plus some more in else branch). + isSampledIn = SamplingScoreGenerator.GetSamplingScore(item) < this.ProactiveSamplingPercentage; + } + else + { + isSampledIn = SamplingScoreGenerator.GetSamplingScore(item) < samplingPercentage; + } if (isSampledIn) { @@ -201,52 +226,13 @@ public void Process(ITelemetry item) } } - private SamplingTelemetryItemTypes PopulateSamplingFlagsFromTheInput(string input) - { - SamplingTelemetryItemTypes samplingTypeFlags = SamplingTelemetryItemTypes.None; - - if (!string.IsNullOrEmpty(input)) - { - string[] splitList = input.Split(this.listSeparators, StringSplitOptions.RemoveEmptyEntries); - foreach (string item in splitList) - { - if (this.allowedTypes.ContainsKey(item)) - { - switch (item.ToUpperInvariant()) - { - case RequestTelemetryName: - samplingTypeFlags |= SamplingTelemetryItemTypes.Request; - break; - case DependencyTelemetryName: - samplingTypeFlags |= SamplingTelemetryItemTypes.RemoteDependency; - break; - case ExceptionTelemetryName: - samplingTypeFlags |= SamplingTelemetryItemTypes.Exception; - break; - case PageViewTelemetryName: - samplingTypeFlags |= SamplingTelemetryItemTypes.PageView; - break; - case TraceTelemetryName: - samplingTypeFlags |= SamplingTelemetryItemTypes.Message; - break; - case EventTelemetryName: - samplingTypeFlags |= SamplingTelemetryItemTypes.Event; - break; - } - } - } - } - - return samplingTypeFlags; - } - private void HandlePossibleProactiveSampling(ITelemetry item, double currentSamplingPercentage, ISupportAdvancedSampling samplingSupportingTelemetry = null) { var advancedSamplingSupportingTelemetry = samplingSupportingTelemetry ?? item as ISupportAdvancedSampling; if (advancedSamplingSupportingTelemetry != null) { - if (advancedSamplingSupportingTelemetry.IsSampledOutAtHead) + if (advancedSamplingSupportingTelemetry.ProactiveSamplingDecision == SamplingDecision.SampledOut) { // Item is sampled in but was proactively sampled out: store the amount of items it represented and drop it this.proactivelySampledOutCounters.AddItems(advancedSamplingSupportingTelemetry.ItemTypeFlag, Convert.ToInt64(100 / currentSamplingPercentage)); @@ -281,17 +267,15 @@ private void HandlePossibleProactiveSampling(ITelemetry item, double currentSamp private bool IsSamplingApplicable(SamplingTelemetryItemTypes telemetryItemTypeFlag) { - if (this.excludedTypesFlags.HasFlag(telemetryItemTypeFlag)) + if (this.includedTypesFlags == SamplingTelemetryItemTypes.None) { - return false; + // default value + return true; } - - if (this.includedTypesFlags != SamplingTelemetryItemTypes.None && !this.includedTypesFlags.HasFlag(telemetryItemTypeFlag)) + else { - return false; + return this.includedTypesFlags.HasFlag(telemetryItemTypeFlag); } - - return true; } } } diff --git a/src/ServerTelemetryChannel/TelemetryChannel.csproj b/src/ServerTelemetryChannel/TelemetryChannel.csproj index b512af1283..f8d33892e5 100644 --- a/src/ServerTelemetryChannel/TelemetryChannel.csproj +++ b/src/ServerTelemetryChannel/TelemetryChannel.csproj @@ -26,20 +26,24 @@ - + All - - all - All - + All - + + All + + + + + + All @@ -90,6 +94,8 @@ + + \ No newline at end of file diff --git a/src/ServerTelemetryChannel/TelemetryProcessorChainBuilderExtensions.cs b/src/ServerTelemetryChannel/TelemetryProcessorChainBuilderExtensions.cs index a0813897c8..73aa1fa4aa 100644 --- a/src/ServerTelemetryChannel/TelemetryProcessorChainBuilderExtensions.cs +++ b/src/ServerTelemetryChannel/TelemetryProcessorChainBuilderExtensions.cs @@ -4,9 +4,7 @@ using System.ComponentModel; using Microsoft.ApplicationInsights.Extensibility.Implementation; - using Microsoft.ApplicationInsights.WindowsServer.Channel.Implementation; using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel; - using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.Implementation; /// /// Extension methods for . @@ -33,6 +31,7 @@ public static TelemetryProcessorChainBuilder UseSampling(this TelemetryProcessor return builder.Use(next => new SamplingTelemetryProcessor(next) { SamplingPercentage = samplingPercentage, + ProactiveSamplingPercentage = null, ExcludedTypes = excludedTypes, IncludedTypes = includedTypes, });