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