diff --git a/CHANGELOG.md b/CHANGELOG.md index 9297d79fd9..932a09cccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This changelog will be used to generate documentation on [release notes page](ht - [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) ## Version 2.11.0-beta1 - [Performance fixes: Support Head Sampling; Remove NewGuid(); Sampling Flags; etc... ](https://github.com/microsoft/ApplicationInsights-dotnet/pull/1158) diff --git a/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt b/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt index 525fa218c3..59a7caf3a8 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/net45/PublicAPI.Unshipped.txt @@ -29,22 +29,6 @@ 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.TaskTimer.TaskTimer() -> void Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void @@ -52,4 +36,24 @@ Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrappe 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 \ No newline at end of file +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 8ca2b400ef..59a7caf3a8 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/net46/PublicAPI.Unshipped.txt @@ -29,23 +29,6 @@ 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.TaskTimer.TaskTimer() -> void Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void @@ -53,4 +36,24 @@ Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrappe 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 \ No newline at end of file +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 8ca2b400ef..59a7caf3a8 100644 --- a/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard1.3/PublicAPI.Unshipped.txt +++ b/PublicAPI/Microsoft.ApplicationInsights.dll/netstandard1.3/PublicAPI.Unshipped.txt @@ -29,23 +29,6 @@ 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.TaskTimer.TaskTimer() -> void Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void @@ -53,4 +36,24 @@ Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrappe 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 \ No newline at end of file +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 295f21c8fc..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,26 +46,6 @@ 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 - Microsoft.ApplicationInsights.Extensibility.Implementation.TaskTimer.TaskTimer() -> void Microsoft.ApplicationInsights.Extensibility.W3C.W3COperationCorrelationTelemetryInitializer.W3COperationCorrelationTelemetryInitializer() -> void Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryDebugWriter.TelemetryDebugWriter() -> void @@ -70,4 +53,24 @@ Microsoft.ApplicationInsights.Extensibility.Implementation.HttpWebResponseWrappe 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 \ No newline at end of file +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/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/OperationCorrelationTelemetryInitializerTests.cs b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs index e10f802a48..33ebbef215 100644 --- a/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs +++ b/Test/Microsoft.ApplicationInsights.Test/Shared/Extensibility/OperationCorrelationTelemetryInitializerTests.cs @@ -267,7 +267,7 @@ public void InitializeSetsBaggage() } [TestMethod] - public void InitilaizeWithActivityWinsOverCallContext() + public void InitializeWithActivityWinsOverCallContext() { CallContextHelpers.SaveOperationContext(new OperationContextForCallContext { RootOperationId = "callContextRoot" }); var currentActivity = new Activity("test"); @@ -285,5 +285,67 @@ public void InitilaizeWithActivityWinsOverCallContext() Assert.AreEqual("v1", telemetry.Properties["k1"]); currentActivity.Stop(); } + + [TestMethod] + public void InitializeWithActivityRecorded() + { + var currentActivity = new Activity("test"); + currentActivity.ActivityTraceFlags |= ActivityTraceFlags.Recorded; + currentActivity.Start(); + var request = new RequestTelemetry(); + + (new OperationCorrelationTelemetryInitializer()).Initialize(request); + + Assert.AreEqual(SamplingDecision.SampledIn, request.ProactiveSamplingDecision); + currentActivity.Stop(); + } + + [TestMethod] + public void InitializeWithActivityNotRecorded() + { + var currentActivity = new Activity("test"); + currentActivity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded; + currentActivity.Start(); + var telemetry = new RequestTelemetry(); + + (new OperationCorrelationTelemetryInitializer()).Initialize(telemetry); + + 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(); + } } } \ No newline at end of file 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 78b54ad218..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/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/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/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 TelemetryTypeSupportsSampling(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/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 IDictionary Properties 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 SeverityLevel? SeverityLevel 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 IDictionary Properties 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 IDictionary Properties 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 IDictionary Properties 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/OperationCorrelationTelemetryInitializer.cs b/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs index fd49ac9c07..6759115e95 100644 --- a/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs +++ b/src/Microsoft.ApplicationInsights/Extensibility/OperationCorrelationTelemetryInitializer.cs @@ -71,6 +71,13 @@ public void Initialize(ITelemetry telemetryItem) itemOperationContext.Name = operationName; } } + + if (currentActivity.Recorded && + telemetryItem is ISupportAdvancedSampling supportSamplingTelemetry && + supportSamplingTelemetry.ProactiveSamplingDecision == SamplingDecision.None) + { + supportSamplingTelemetry.ProactiveSamplingDecision = SamplingDecision.SampledIn; + } } }); diff --git a/src/Microsoft.ApplicationInsights/TelemetryClient.cs b/src/Microsoft.ApplicationInsights/TelemetryClient.cs index 9bfe645365..5fdf86697e 100644 --- a/src/Microsoft.ApplicationInsights/TelemetryClient.cs +++ b/src/Microsoft.ApplicationInsights/TelemetryClient.cs @@ -481,14 +481,17 @@ public void Initialize(ITelemetry telemetry) 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")) { 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( this.samplingProcessor = new SamplingTelemetryProcessor(next, this.estimatorProcessor) { SamplingPercentage = this.estimatorSettings.InitialSamplingPercentage, + ProactiveSamplingPercentage = null, }; } @@ -280,6 +280,7 @@ private void SamplingPercentageChanged( if (isSamplingPercentageChanged) { this.samplingProcessor.SamplingPercentage = newSamplingPercentage; + this.samplingProcessor.ProactiveSamplingPercentage = 100 / this.estimatorProcessor.CurrentProactiveSamplingRate; TelemetryChannelEventSource.Log.SamplingChanged(newSamplingPercentage); } 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( 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( /// /// 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/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 8bedd1b7b3..2a2d1ae7ee 100644 --- a/src/ServerTelemetryChannel/SamplingTelemetryProcessor.cs +++ b/src/ServerTelemetryChannel/SamplingTelemetryProcessor.cs @@ -28,13 +28,9 @@ 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; } @@ -118,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. /// @@ -182,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) { @@ -212,7 +232,7 @@ private void HandlePossibleProactiveSampling(ITelemetry item, double currentSamp 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)); 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, });