From 6ae33e034b7b36a55f7c841218da987cdd0736c6 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Thu, 27 Apr 2017 13:23:01 -0700 Subject: [PATCH] Track dependency instrumented with HttpDesktop DiagnosticSource in DiagnosticSource Response event (#509) --- ...endencyTrackingTelemetryModuleTestNet46.cs | 18 +- ...DependencyCollector.Shared.Tests.projitems | 1 + ...ktopDiagnosticSourceHttpProcessingTests.cs | 306 ++++++++++++++++++ .../FrameworkHttpProcessingTest.cs | 278 ++-------------- .../DependencyCollector.Shared.projitems | 1 + .../DependencyTrackingTelemetryModule.cs | 18 +- .../ClientServerDependencyTracker.cs | 8 +- .../Implementation/DependencyTableStore.cs | 1 + .../DesktopDiagnosticSourceHttpProcessing.cs | 81 +++++ .../Implementation/FrameworkHttpProcessing.cs | 79 +---- .../HttpDesktopDiagnosticSourceListener.cs | 6 +- .../HttpDesktopDiagnosticSourceSubscriber.cs | 1 + .../Shared/Implementation/HttpProcessing.cs | 15 +- .../Implementation/ProfilerHttpProcessing.cs | 12 +- 14 files changed, 468 insertions(+), 357 deletions(-) create mode 100644 Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs create mode 100644 Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs diff --git a/Src/DependencyCollector/Net46.Tests/DependencyTrackingTelemetryModuleTestNet46.cs b/Src/DependencyCollector/Net46.Tests/DependencyTrackingTelemetryModuleTestNet46.cs index 297950bc8..79747c6ce 100644 --- a/Src/DependencyCollector/Net46.Tests/DependencyTrackingTelemetryModuleTestNet46.cs +++ b/Src/DependencyCollector/Net46.Tests/DependencyTrackingTelemetryModuleTestNet46.cs @@ -2,6 +2,7 @@ { using System; using System.Diagnostics; + using System.IO; using System.Net; using Microsoft.ApplicationInsights.Channel; @@ -54,9 +55,13 @@ public void TestDependencyCollectionNoParentActivity() module.ProfileQueryEndpoint = FakeProfileApiEndpoint; module.Initialize(config); - var url = new Uri("http://bing.com"); + var url = new Uri("https://www.bing.com/"); HttpWebRequest request = WebRequest.CreateHttp(url); - request.GetResponse(); + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + using (var reader = new StreamReader(response.GetResponseStream())) + { + reader.ReadToEnd(); + } Assert.IsNotNull(sentTelemetry); var item = (DependencyTelemetry)sentTelemetry; @@ -118,11 +123,16 @@ public void TestDependencyCollectionWithParentActivity() module.ProfileQueryEndpoint = FakeProfileApiEndpoint; module.Initialize(config); - var url = new Uri("http://bing.com"); + var url = new Uri("https://www.bing.com/"); HttpWebRequest request = WebRequest.CreateHttp(url); var parent = new Activity("parent").AddBaggage("k", "v").SetParentId("|guid.").Start(); - request.GetResponse(); + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + using (var reader = new StreamReader(response.GetResponseStream())) + { + reader.ReadToEnd(); + } + parent.Stop(); Assert.IsNotNull(sentTelemetry); diff --git a/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems b/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems index 97548e932..6cb167c64 100644 --- a/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems +++ b/Src/DependencyCollector/Shared.Tests/DependencyCollector.Shared.Tests.projitems @@ -14,6 +14,7 @@ + diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs b/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs new file mode 100644 index 000000000..157a09052 --- /dev/null +++ b/Src/DependencyCollector/Shared.Tests/Implementation/DesktopDiagnosticSourceHttpProcessingTests.cs @@ -0,0 +1,306 @@ +#if NET45 +namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.ApplicationInsights.Channel; + using Microsoft.ApplicationInsights.Common; + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.DependencyCollector.Implementation.Operation; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.Extensibility.Implementation; + using Microsoft.ApplicationInsights.TestFramework; + using Microsoft.ApplicationInsights.Web.TestFramework; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DesktopDiagnosticSourceHttpProcessingTests + { + #region Fields + private const string RandomAppIdEndpoint = "http://app.id.endpoint"; // appIdEndpoint - this really won't be used for tests because of the app id provider override. + private const int TimeAccuracyMilliseconds = 50; + private Uri testUrl = new Uri("http://www.microsoft.com/"); + private int sleepTimeMsecBetweenBeginAndEnd = 100; + private TelemetryConfiguration configuration; + private List sendItems; + private DesktopDiagnosticSourceHttpProcessing httpProcessingFramework; + #endregion //Fields + + #region TestInitialize + + [TestInitialize] + public void TestInitialize() + { + this.configuration = new TelemetryConfiguration(); + this.sendItems = new List(); + this.configuration.TelemetryChannel = new StubTelemetryChannel { OnSend = item => this.sendItems.Add(item) }; + this.configuration.InstrumentationKey = Guid.NewGuid().ToString(); + this.httpProcessingFramework = new DesktopDiagnosticSourceHttpProcessing(this.configuration, new ObjectInstanceBasedOperationHolder(), /*setCorrelationHeaders*/ true, new List(), RandomAppIdEndpoint); + var correlationIdLookupHelper = new CorrelationIdLookupHelper((string ikey) => + { + // Pretend App Id is the same as Ikey + var tcs = new TaskCompletionSource(); + tcs.SetResult(ikey); + return tcs.Task; + }); + this.httpProcessingFramework.OverrideCorrelationIdLookupHelper(correlationIdLookupHelper); + } + + [TestCleanup] + public void Cleanup() + { + } + #endregion //TestInitiliaze + + /// + /// Validates that OnRequestSend and OnResponseReceive sends valid telemetry. + /// + [TestMethod] + public void RddTestHttpProcessingFrameworkUpdateTelemetryName() + { + var stopwatch = Stopwatch.StartNew(); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); + this.httpProcessingFramework.OnRequestSend(request); + Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); + Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); + var response = TestUtils.GenerateHttpWebResponse(HttpStatusCode.OK); + stopwatch.Stop(); + this.httpProcessingFramework.OnResponseReceive(request, response); + + Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); + ValidateTelemetryPacketForOnRequestSend(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, true, stopwatch.Elapsed.TotalMilliseconds, "200"); + } + + /// + /// Validates that even if multiple events have fired, as long as there is only + /// one HttpWebRequest, only one event should be written, during the first call + /// to OnResponseReceive. + /// + [TestMethod] + public void RddTestHttpProcessingFrameworkNoDuplication() + { + Stopwatch stopwatch = Stopwatch.StartNew(); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); + var redirectResponse = TestUtils.GenerateHttpWebResponse(HttpStatusCode.Redirect); + var successResponse = TestUtils.GenerateHttpWebResponse(HttpStatusCode.OK); + + this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnRequestSend(request); + Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); + Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); + this.httpProcessingFramework.OnResponseReceive(request, redirectResponse); + Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); + + this.httpProcessingFramework.OnResponseReceive(request, redirectResponse); + this.httpProcessingFramework.OnResponseReceive(request, successResponse); + stopwatch.Stop(); + + Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); + ValidateTelemetryPacketForOnRequestSend(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, true, stopwatch.Elapsed.TotalMilliseconds, "302"); + } + + /// + /// Validates if DependencyTelemetry sent contains the cross component correlation ID. + /// + [TestMethod] + [Description("Validates if DependencyTelemetry sent contains the cross component correlation ID.")] + public void RddTestHttpProcessingFrameworkOnEndAddsAppIdToTargetField() + { + // Here is a sample App ID, since the test initialize method adds a random ikey and our mock getAppId method pretends that the appId for a given ikey is the same as the ikey. + // This will not match the current component's App ID. Hence represents an external component. + string appId = "0935FC42-FE1A-4C67-975C-0C9D5CBDEE8E"; + + var request = WebRequest.Create(this.testUrl); + + Dictionary headers = new Dictionary + { + { RequestResponseHeaders.RequestContextHeader, this.GetCorrelationIdHeaderValue(appId) } + }; + + var response = TestUtils.GenerateHttpWebResponse(HttpStatusCode.OK, headers); + + this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnResponseReceive(request, response); + Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); + Assert.AreEqual(this.testUrl.Host + " | " + this.GetCorrelationIdValue(appId), ((DependencyTelemetry)this.sendItems[0]).Target); + } + + /// + /// Ensures that the source request header is added when request is sent. + /// + [TestMethod] + [Description("Ensures that the source request header is added when request is sent.")] + public void RddTestHttpProcessingFrameworkOnBeginAddsSourceHeader() + { + var request = WebRequest.Create(this.testUrl); + + Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); + + this.httpProcessingFramework.OnRequestSend(request); + Assert.IsNotNull(request.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey)); + } + + /// + /// Ensures that the parent id header is added when request is sent. + /// + [TestMethod] + public void RddTestHttpProcessingFrameworkOnBeginAddsParentIdHeader() + { + var request = WebRequest.Create(this.testUrl); + + Assert.IsNull(request.Headers[RequestResponseHeaders.StandardParentIdHeader]); + + var client = new TelemetryClient(this.configuration); + using (var op = client.StartOperation("request")) + { + this.httpProcessingFramework.OnRequestSend(request); + + var actualParentIdHeader = request.Headers[RequestResponseHeaders.StandardParentIdHeader]; + var actualRequestIdHeader = request.Headers[RequestResponseHeaders.RequestIdHeader]; + Assert.IsNotNull(actualParentIdHeader); + Assert.AreNotEqual(actualParentIdHeader, op.Telemetry.Context.Operation.Id); + + Assert.AreEqual(actualParentIdHeader, actualRequestIdHeader); +#if NET45 + Assert.IsTrue(actualRequestIdHeader.StartsWith(Activity.Current.Id, StringComparison.Ordinal)); + Assert.AreNotEqual(Activity.Current.Id, actualRequestIdHeader); +#else + Assert.AreEqual(op.Telemetry.Context.Operation.Id, ApplicationInsightsActivity.GetRootId(request.Headers[RequestResponseHeaders.StandardParentIdHeader])); +#endif + // This code should go away when Activity is fixed: https://github.com/dotnet/corefx/issues/18418 + // check that Ids are not generated by Activity + // so they look like OperationTelemetry.Id + var operationId = op.Telemetry.Context.Operation.Id; + + // length is like default RequestTelemetry.Id length + Assert.AreEqual(new DependencyTelemetry().Id.Length, operationId.Length); + + // operationId is ulong base64 encoded + byte[] data = Convert.FromBase64String(operationId); + Assert.AreEqual(8, data.Length); + BitConverter.ToUInt64(data, 0); + + // does not look like root Id generated by Activity + Assert.AreEqual(1, operationId.Split('-').Length); + + //// end of workaround test + } + } + + /// + /// Ensures that the source request header is not added, as per the config, when request is sent. + /// + [TestMethod] + [Description("Ensures that the source request header is not added when the config commands as such")] + public void RddTestHttpProcessingFrameworkOnBeginSkipsAddingSourceHeaderPerConfig() + { + string hostnamepart = "partofhostname"; + string url = string.Format(CultureInfo.InvariantCulture, "http://hostnamestart{0}hostnameend.com/path/to/something?param=1", hostnamepart); + var request = WebRequest.Create(new Uri(url)); + + Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); + Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count()); + + var localHttpProcessingFramework = new DesktopDiagnosticSourceHttpProcessing( + this.configuration, + new ObjectInstanceBasedOperationHolder(), + false, + new List(), + RandomAppIdEndpoint); + + localHttpProcessingFramework.OnRequestSend(request); + Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); + Assert.AreEqual(0, request.Headers.Keys.Cast().Count(x => x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase))); + + ICollection exclusionList = new SanitizedHostList() { "randomstringtoexclude", hostnamepart }; + localHttpProcessingFramework = new DesktopDiagnosticSourceHttpProcessing( + this.configuration, + new ObjectInstanceBasedOperationHolder(), + true, + exclusionList, + RandomAppIdEndpoint); + localHttpProcessingFramework.OnRequestSend(request); + Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); + Assert.AreEqual(0, request.Headers.Keys.Cast().Count(x => x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase))); + } + + /// + /// Ensures that the source request header is not overwritten if already provided by the user. + /// + [TestMethod] + [Description("Ensures that the source request header is not overwritten if already provided by the user.")] + public void RddTestHttpProcessingFrameworkOnBeginDoesNotOverwriteExistingSource() + { + string sampleHeaderValueWithAppId = RequestResponseHeaders.RequestContextCorrelationSourceKey + "=HelloWorld"; + var request = WebRequest.Create(this.testUrl); + + request.Headers.Add(RequestResponseHeaders.RequestContextHeader, sampleHeaderValueWithAppId); + + this.httpProcessingFramework.OnRequestSend(request); + var actualHeaderValue = request.Headers[RequestResponseHeaders.RequestContextHeader]; + + Assert.IsNotNull(actualHeaderValue); + Assert.AreEqual(sampleHeaderValueWithAppId, actualHeaderValue); + + string sampleHeaderValueWithoutAppId = "helloWorld"; + request = WebRequest.Create(this.testUrl); + + request.Headers.Add(RequestResponseHeaders.RequestContextHeader, sampleHeaderValueWithoutAppId); + + this.httpProcessingFramework.OnRequestSend(request); + actualHeaderValue = request.Headers[RequestResponseHeaders.RequestContextHeader]; + + Assert.IsNotNull(actualHeaderValue); + Assert.AreNotEqual(sampleHeaderValueWithAppId, actualHeaderValue); + } + + private static void ValidateTelemetryPacketForOnRequestSend(DependencyTelemetry remoteDependencyTelemetryActual, Uri url, string kind, bool? success, double valueMin, string statusCode) + { + Assert.AreEqual("GET " + url.AbsolutePath, remoteDependencyTelemetryActual.Name, true, "Resource name in the sent telemetry is wrong"); + string expectedVersion = + SdkVersionHelper.GetExpectedSdkVersion(typeof(DependencyTrackingTelemetryModule), prefix: "rddfd:"); + ValidateTelemetryPacket(remoteDependencyTelemetryActual, url, kind, success, valueMin, statusCode, expectedVersion); + } + + private static void ValidateTelemetryPacket(DependencyTelemetry remoteDependencyTelemetryActual, Uri url, string kind, bool? success, double valueMin, string statusCode, string expectedVersion) + { + Assert.AreEqual(url.Host, remoteDependencyTelemetryActual.Target, true, "Resource target in the sent telemetry is wrong"); + Assert.AreEqual(url.OriginalString, remoteDependencyTelemetryActual.Data, true, "Resource data in the sent telemetry is wrong"); + Assert.AreEqual(kind.ToString(), remoteDependencyTelemetryActual.Type, "DependencyKind in the sent telemetry is wrong"); + Assert.AreEqual(success, remoteDependencyTelemetryActual.Success, "Success in the sent telemetry is wrong"); + Assert.AreEqual(statusCode, remoteDependencyTelemetryActual.ResultCode, "ResultCode in the sent telemetry is wrong"); + + var valueMinRelaxed = valueMin - TimeAccuracyMilliseconds; + Assert.IsTrue( + remoteDependencyTelemetryActual.Duration >= TimeSpan.FromMilliseconds(valueMinRelaxed), + string.Format(CultureInfo.InvariantCulture, "Value (dependency duration = {0}) in the sent telemetry should be equal or more than the time duration between start and end", remoteDependencyTelemetryActual.Duration)); + + var valueMax = valueMin + TimeAccuracyMilliseconds; + Assert.IsTrue( + remoteDependencyTelemetryActual.Duration <= TimeSpan.FromMilliseconds(valueMax), + string.Format(CultureInfo.InvariantCulture, "Value (dependency duration = {0}) in the sent telemetry should not be signigficantly bigger then the time duration between start and end", remoteDependencyTelemetryActual.Duration)); + + Assert.AreEqual(expectedVersion, remoteDependencyTelemetryActual.Context.GetInternalContext().SdkVersion); + } + + private string GetCorrelationIdValue(string appId) + { + return string.Format(CultureInfo.InvariantCulture, "cid-v1:{0}", appId); + } + + private string GetCorrelationIdHeaderValue(string appId) + { + return string.Format(CultureInfo.InvariantCulture, "{0}=cid-v1:{1}", RequestResponseHeaders.RequestContextCorrelationTargetKey, appId); + } + } +} +#endif \ No newline at end of file diff --git a/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkHttpProcessingTest.cs b/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkHttpProcessingTest.cs index 248431ff9..a1fcee004 100644 --- a/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkHttpProcessingTest.cs +++ b/Src/DependencyCollector/Shared.Tests/Implementation/FrameworkHttpProcessingTest.cs @@ -47,11 +47,13 @@ public void TestInitialize() this.configuration.InstrumentationKey = Guid.NewGuid().ToString(); this.httpProcessingFramework = new FrameworkHttpProcessing(this.configuration, new CacheBasedOperationHolder("testCache", 100 * 1000), /*setCorrelationHeaders*/ true, new List(), RandomAppIdEndpoint); this.httpProcessingFramework.OverrideCorrelationIdLookupHelper(new CorrelationIdLookupHelper(new Dictionary { { this.configuration.InstrumentationKey, "cid-v1:" + this.configuration.InstrumentationKey } })); + DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated = false; } [TestCleanup] public void Cleanup() { + DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated = false; } #endregion //TestInitiliaze @@ -164,7 +166,7 @@ public void RddTestHttpProcessingFrameworkOnEndHttpCallbackInvalidId() HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); var id1 = ClientServerDependencyTracker.GetIdForRequestObject(request); var id2 = 200; - this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnBeginHttpCallback(id1, this.testUrl.ToString()); Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); @@ -180,7 +182,7 @@ public void OnEndHttpCallbackSetsSuccessToFalseForNegativeStatusCode() HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); var id = ClientServerDependencyTracker.GetIdForRequestObject(request); - this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.ToString()); this.httpProcessingFramework.OnEndHttpCallback(id, null, false, statusCode); Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); @@ -196,7 +198,7 @@ public void OnEndHttpCallbackSetsSuccessToTrueForLessThan400() HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); var id = ClientServerDependencyTracker.GetIdForRequestObject(request); - this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.ToString()); this.httpProcessingFramework.OnEndHttpCallback(id, null, false, statusCode); Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); @@ -205,6 +207,19 @@ public void OnEndHttpCallbackSetsSuccessToTrueForLessThan400() Assert.IsTrue(actual.Success.Value); } + [TestMethod] + public void FrameworkHttpProcessingIsDisabledWhenHttpDesktopDiagSourceIsEnabled() + { + DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated = true; + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); + var id = ClientServerDependencyTracker.GetIdForRequestObject(request); + this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.ToString()); + this.httpProcessingFramework.OnEndHttpCallback(id, null, false, 200); + + Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be sent"); + } + [TestMethod] public void OnEndHttpCallbackSetsSuccessToFalseForMoreThan400() { @@ -224,7 +239,7 @@ public void HttpProcessorSetsTargetForNonStandardPort() { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrlNonStandardPort); var id = ClientServerDependencyTracker.GetIdForRequestObject(request); - this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrlNonStandardPort.ToString()); this.httpProcessingFramework.OnEndHttpCallback(id, null, false, 500); Assert.AreEqual(1, this.sendItems.Count, "Exactly one telemetry item should be sent"); @@ -233,222 +248,6 @@ public void HttpProcessorSetsTargetForNonStandardPort() Assert.AreEqual(expectedTarget, receivedItem.Target, "HttpProcessingFramework returned incorrect target for non standard port."); } - /// - /// Validates that even if OnBeginHttpCallback is called after OnRequestSend, the - /// packet should match what's written during OnRequestSend. - /// - [TestMethod] - public void RddTestHttpProcessingFrameworkUpdateTelemetryName() - { - Stopwatch stopwatch = Stopwatch.StartNew(); - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); - var id = ClientServerDependencyTracker.GetIdForRequestObject(request); - this.httpProcessingFramework.OnRequestSend(request); - this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.OriginalString); - Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); - Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); - this.httpProcessingFramework.OnEndHttpCallback(id, true, false, 200); - stopwatch.Stop(); - - Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); - ValidateTelemetryPacketForOnRequestSend(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, true, stopwatch.Elapsed.TotalMilliseconds, "200"); - } - - /// - /// Validates that even if multiple events have fired, as long as there is only - /// one HttpWebRequest, only one event should be written, during the first call - /// to OnEndHttpCallback. - /// - [TestMethod] - public void RddTestHttpProcessingFrameworkNoDuplication() - { - Stopwatch stopwatch = Stopwatch.StartNew(); - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); - var id = ClientServerDependencyTracker.GetIdForRequestObject(request); - var returnObject = TestUtils.GenerateHttpWebResponse(HttpStatusCode.BadRequest); - - this.httpProcessingFramework.OnRequestSend(request); - this.httpProcessingFramework.OnRequestSend(request); - this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.OriginalString); - this.httpProcessingFramework.OnRequestSend(request); - this.httpProcessingFramework.OnRequestSend(request); - this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.OriginalString); - this.httpProcessingFramework.OnBeginHttpCallback(id, this.testUrl.OriginalString); - this.httpProcessingFramework.OnRequestSend(request); - Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); - Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); - this.httpProcessingFramework.OnResponseReceive(request, returnObject); - this.httpProcessingFramework.OnEndHttpCallback(id, false, false, 409); - this.httpProcessingFramework.OnEndHttpCallback(id, true, false, 304); - this.httpProcessingFramework.OnResponseReceive(request, returnObject); - this.httpProcessingFramework.OnEndHttpCallback(id, true, false, 200); - this.httpProcessingFramework.OnResponseReceive(request, returnObject); - this.httpProcessingFramework.OnEndHttpCallback(id, false, false, 400); - stopwatch.Stop(); - - Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); - ValidateTelemetryPacketForOnRequestSend(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, false, stopwatch.Elapsed.TotalMilliseconds, "409"); - } - - /// - /// Validates if DependencyTelemetry sent contains the cross component correlation ID. - /// - [TestMethod] - [Description("Validates if DependencyTelemetry sent contains the cross component correlation ID.")] - public void RddTestHttpProcessingFrameworkOnEndAddsAppIdToTargetField() - { - // Here is a sample App ID, since the test initialize method adds a random ikey and our mock getAppId method pretends that the appId for a given ikey is the same as the ikey. - // This will not match the current component's App ID. Hence represents an external component. - string appId = "0935FC42-FE1A-4C67-975C-0C9D5CBDEE8E"; - - this.SimulateWebRequestResponseWithAppId(appId); - - Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); - Assert.AreEqual(this.testUrl.Host + " | " + this.GetCorrelationIdValue(appId), ((DependencyTelemetry)this.sendItems[0]).Target); - } - - /// - /// Ensures that the source request header is added when request is sent. - /// - [TestMethod] - [Description("Ensures that the source request header is added when request is sent.")] - public void RddTestHttpProcessingFrameworkOnBeginAddsSourceHeader() - { - var request = WebRequest.Create(this.testUrl); - - Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); - - this.httpProcessingFramework.OnRequestSend(request); - Assert.IsNotNull(request.Headers.GetNameValueHeaderValue(RequestResponseHeaders.RequestContextHeader, RequestResponseHeaders.RequestContextCorrelationSourceKey)); - } - - /// - /// Ensures that the parent id header is added when request is sent. - /// - [TestMethod] - public void RddTestHttpProcessingFrameworkOnBeginAddsParentIdHeader() - { - var request = WebRequest.Create(this.testUrl); - - Assert.IsNull(request.Headers[RequestResponseHeaders.StandardParentIdHeader]); - - var client = new TelemetryClient(this.configuration); - using (var op = client.StartOperation("request")) - { - this.httpProcessingFramework.OnRequestSend(request); - - var actualParentIdHeader = request.Headers[RequestResponseHeaders.StandardParentIdHeader]; - var actualRequestIdHeader = request.Headers[RequestResponseHeaders.RequestIdHeader]; - Assert.IsNotNull(actualParentIdHeader); - Assert.AreNotEqual(actualParentIdHeader, op.Telemetry.Context.Operation.Id); - - Assert.AreEqual(actualParentIdHeader, actualRequestIdHeader); -#if NET45 - Assert.IsTrue(actualRequestIdHeader.StartsWith(Activity.Current.Id, StringComparison.Ordinal)); - Assert.AreNotEqual(Activity.Current.Id, actualRequestIdHeader); -#else - Assert.AreEqual(op.Telemetry.Context.Operation.Id, ApplicationInsightsActivity.GetRootId(request.Headers[RequestResponseHeaders.StandardParentIdHeader])); -#endif - // This code should go away when Activity is fixed: https://github.com/dotnet/corefx/issues/18418 - // check that Ids are not generated by Activity - // so they look like OperationTelemetry.Id - var operationId = op.Telemetry.Context.Operation.Id; - - // length is like default RequestTelemetry.Id length - Assert.AreEqual(new DependencyTelemetry().Id.Length, operationId.Length); - - // operationId is ulong base64 encoded - byte[] data = Convert.FromBase64String(operationId); - Assert.AreEqual(8, data.Length); - BitConverter.ToUInt64(data, 0); - - // does not look like root Id generated by Activity - Assert.AreEqual(1, operationId.Split('-').Length); - - //// end of workaround test - } - } - - /// - /// Ensures that the source request header is not added, as per the config, when request is sent. - /// - [TestMethod] - [Description("Ensures that the source request header is not added when the config commands as such")] - public void RddTestHttpProcessingFrameworkOnBeginSkipsAddingSourceHeaderPerConfig() - { - string hostnamepart = "partofhostname"; - string url = string.Format(CultureInfo.InvariantCulture, "http://hostnamestart{0}hostnameend.com/path/to/something?param=1", hostnamepart); - var request = WebRequest.Create(new Uri(url)); - - Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); - Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count()); - - var httpProcessingFramework = new FrameworkHttpProcessing(this.configuration, new CacheBasedOperationHolder("tempCache1", 100 * 1000), /*setCorrelationHeaders*/ false, new List(), RandomAppIdEndpoint); - httpProcessingFramework.OnRequestSend(request); - Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); - Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count()); - - ICollection exclusionList = new SanitizedHostList() { "randomstringtoexclude", hostnamepart }; - httpProcessingFramework = new FrameworkHttpProcessing(this.configuration, new CacheBasedOperationHolder("tempCache2", 100 * 1000), /*setCorrelationHeaders*/ true, exclusionList, RandomAppIdEndpoint); - httpProcessingFramework.OnRequestSend(request); - Assert.IsNull(request.Headers[RequestResponseHeaders.RequestContextHeader]); - Assert.AreEqual(0, request.Headers.Keys.Cast().Where((x) => { return x.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase); }).Count()); - } - - /// - /// Ensures that the source request header is not overwritten if already provided by the user. - /// - [TestMethod] - [Description("Ensures that the source request header is not overwritten if already provided by the user.")] - public void RddTestHttpProcessingFrameworkOnBeginDoesNotOverwriteExistingSource() - { - string sampleHeaderValueWithAppId = RequestResponseHeaders.RequestContextCorrelationSourceKey + "=HelloWorld"; - var request = WebRequest.Create(this.testUrl); - - request.Headers.Add(RequestResponseHeaders.RequestContextHeader, sampleHeaderValueWithAppId); - - this.httpProcessingFramework.OnRequestSend(request); - var actualHeaderValue = request.Headers[RequestResponseHeaders.RequestContextHeader]; - - Assert.IsNotNull(actualHeaderValue); - Assert.AreEqual(sampleHeaderValueWithAppId, actualHeaderValue); - - string sampleHeaderValueWithoutAppId = "helloWorld"; - request = WebRequest.Create(this.testUrl); - - request.Headers.Add(RequestResponseHeaders.RequestContextHeader, sampleHeaderValueWithoutAppId); - - this.httpProcessingFramework.OnRequestSend(request); - actualHeaderValue = request.Headers[RequestResponseHeaders.RequestContextHeader]; - - Assert.IsNotNull(actualHeaderValue); - Assert.AreNotEqual(sampleHeaderValueWithAppId, actualHeaderValue); - } - - /// - /// Validates HttpProcessingFramework sends correct telemetry on calling OnResponseReceive + OnEndHttpCallback. - /// - [TestMethod] - [Description("Validates HttpProcessingFramework sends correct telemetry on calling OnResponseReceive + OnEndHttpCallback.")] - [Owner("cithomas")] - [TestCategory("CVT")] - public void RddTestHttpProcessingFrameworkOnEndHttpCallback() - { - var request = WebRequest.Create(this.testUrl); - var returnObjectPassed = TestUtils.GenerateHttpWebResponse(HttpStatusCode.OK); - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - this.httpProcessingFramework.OnRequestSend(request); - Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); - Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); - this.httpProcessingFramework.OnResponseReceive(request, returnObjectPassed); - this.httpProcessingFramework.OnEndHttpCallback(ClientServerDependencyTracker.GetIdForRequestObject(request), true, true, 200); - stopwatch.Stop(); - - Assert.AreEqual(1, this.sendItems.Count, "Only one telemetry item should be sent"); - ValidateTelemetryPacketForOnRequestSend(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, true, stopwatch.Elapsed.TotalMilliseconds, "200"); - } - #endregion //BeginEndCallBacks #region AsyncScenarios @@ -468,16 +267,16 @@ public void RddTestHttpProcessingFrameworkStartTimeFromGetRequestStreamAsync() Stopwatch stopwatch = Stopwatch.StartNew(); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.testUrl); var id1 = ClientServerDependencyTracker.GetIdForRequestObject(request); - this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnBeginHttpCallback(id1, this.testUrl.ToString()); Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); - this.httpProcessingFramework.OnRequestSend(request); + this.httpProcessingFramework.OnBeginHttpCallback(id1, this.testUrl.ToString()); Thread.Sleep(this.sleepTimeMsecBetweenBeginAndEnd); Assert.AreEqual(0, this.sendItems.Count, "No telemetry item should be processed without calling End"); this.httpProcessingFramework.OnEndHttpCallback(id1, true, false, 200); stopwatch.Stop(); Assert.AreEqual(1, this.sendItems.Count, "Exactly one telemetry item should be sent"); - ValidateTelemetryPacketForOnRequestSend(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, true, stopwatch.Elapsed.TotalMilliseconds, "200"); + ValidateTelemetryPacketForOnBeginHttpCallback(this.sendItems[0] as DependencyTelemetry, this.testUrl, RemoteDependencyConstants.HTTP, true, stopwatch.Elapsed.TotalMilliseconds, "200"); } #endregion AsyncScenarios @@ -499,13 +298,13 @@ private static void ValidateTelemetryPacketForOnBeginHttpCallback( ValidateTelemetryPacket(remoteDependencyTelemetryActual, url, kind, success, valueMin, statusCode, expectedVersion); } - private static void ValidateTelemetryPacketForOnRequestSend( +/* private static void ValidateTelemetryPacketForOnRequestSend( DependencyTelemetry remoteDependencyTelemetryActual, Uri url, string kind, bool? success, double valueMin, string statusCode) { Assert.AreEqual("GET " + url.AbsolutePath, remoteDependencyTelemetryActual.Name, true, "Resource name in the sent telemetry is wrong"); string expectedVersion = SdkVersionHelper.GetExpectedSdkVersion(typeof(DependencyTrackingTelemetryModule), prefix: "rddfd:"); ValidateTelemetryPacket(remoteDependencyTelemetryActual, url, kind, success, valueMin, statusCode, expectedVersion); - } + }*/ private static void ValidateTelemetryPacket( DependencyTelemetry remoteDependencyTelemetryActual, Uri url, string kind, bool? success, double valueMin, string statusCode, string expectedVersion) @@ -529,35 +328,6 @@ private static void ValidateTelemetryPacket( Assert.AreEqual(expectedVersion, remoteDependencyTelemetryActual.Context.GetInternalContext().SdkVersion); } - private void SimulateWebRequestResponseWithAppId(string appId) - { - var request = WebRequest.Create(this.testUrl); - - Dictionary headers = new Dictionary(); - headers.Add(RequestResponseHeaders.RequestContextHeader, this.GetCorrelationIdHeaderValue(appId)); - - var returnObjectPassed = TestUtils.GenerateHttpWebResponse(HttpStatusCode.OK, headers); - - this.httpProcessingFramework.OnBeginHttpCallback(ClientServerDependencyTracker.GetIdForRequestObject(request), this.testUrl.OriginalString); - this.httpProcessingFramework.OnRequestSend(request); - this.httpProcessingFramework.OnResponseReceive(request, returnObjectPassed); - this.httpProcessingFramework.OnEndHttpCallback( - ClientServerDependencyTracker.GetIdForRequestObject(request), - true, - true, - (int)HttpStatusCode.OK); - } - - private string GetCorrelationIdValue(string appId) - { - return string.Format(CultureInfo.InvariantCulture, "cid-v1:{0}", appId); - } - - private string GetCorrelationIdHeaderValue(string appId) - { - return string.Format(CultureInfo.InvariantCulture, "{0}=cid-v1:{1}", RequestResponseHeaders.RequestContextCorrelationTargetKey, appId); - } - #endregion Helpers } } diff --git a/Src/DependencyCollector/Shared/DependencyCollector.Shared.projitems b/Src/DependencyCollector/Shared/DependencyCollector.Shared.projitems index 20c7db931..26af516e2 100644 --- a/Src/DependencyCollector/Shared/DependencyCollector.Shared.projitems +++ b/Src/DependencyCollector/Shared/DependencyCollector.Shared.projitems @@ -16,6 +16,7 @@ + diff --git a/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs b/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs index 83e92ed4a..e54511eb1 100644 --- a/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs +++ b/Src/DependencyCollector/Shared/DependencyTrackingTelemetryModule.cs @@ -221,9 +221,23 @@ protected virtual void Dispose(bool disposing) /// private void InitializeForFrameworkEventSource() { +#if NET45 + DesktopDiagnosticSourceHttpProcessing desktopHttpProcessing = new DesktopDiagnosticSourceHttpProcessing( + this.telemetryConfiguration, + DependencyTableStore.Instance.WebRequestConditionalHolder, + this.SetComponentCorrelationHttpHeaders, + this.ExcludeComponentCorrelationHttpHeadersOnDomains, + this.EffectiveProfileQueryEndpoint); + this.httpDesktopDiagnosticSourceListener = new HttpDesktopDiagnosticSourceListener(desktopHttpProcessing); +#endif + #if !NET40 - FrameworkHttpProcessing frameworkHttpProcessing = new FrameworkHttpProcessing(this.telemetryConfiguration, DependencyTableStore.Instance.WebRequestCacheHolder, this.SetComponentCorrelationHttpHeaders, this.ExcludeComponentCorrelationHttpHeadersOnDomains, this.EffectiveProfileQueryEndpoint); - this.httpDesktopDiagnosticSourceListener = new HttpDesktopDiagnosticSourceListener(frameworkHttpProcessing); + FrameworkHttpProcessing frameworkHttpProcessing = new FrameworkHttpProcessing( + this.telemetryConfiguration, + DependencyTableStore.Instance.WebRequestCacheHolder, + this.SetComponentCorrelationHttpHeaders, + this.ExcludeComponentCorrelationHttpHeadersOnDomains, + this.EffectiveProfileQueryEndpoint); // In 4.5 EventListener has a race condition issue in constructor so we retry to create listeners this.httpEventListener = RetryPolicy.Retry( diff --git a/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs b/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs index f91f219e2..b1bca3418 100644 --- a/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs +++ b/Src/DependencyCollector/Shared/Implementation/ClientServerDependencyTracker.cs @@ -106,7 +106,7 @@ internal static Tuple GetTupleForWebDependencies(WebR Tuple telemetryTuple = null; - if (DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) + if (DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated || DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) { telemetryTuple = DependencyTableStore.Instance.WebRequestConditionalHolder.Get(webRequest); } @@ -139,7 +139,7 @@ internal static void AddTupleForWebDependencies(WebRequest webRequest, Dependenc } var telemetryTuple = new Tuple(telemetry, isCustomCreated); - if (DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) + if (DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated || DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) { DependencyTableStore.Instance.WebRequestConditionalHolder.Store(webRequest, telemetryTuple); } @@ -165,7 +165,7 @@ internal static Tuple GetTupleForSqlDependencies(SqlC Tuple telemetryTuple = null; - if (DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) + if (DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated || DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) { telemetryTuple = DependencyTableStore.Instance.SqlRequestConditionalHolder.Get(sqlRequest); } @@ -198,7 +198,7 @@ internal static void AddTupleForSqlDependencies(SqlCommand sqlRequest, Dependenc } var telemetryTuple = new Tuple(telemetry, isCustomCreated); - if (DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) + if (DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated || DependencyTableStore.Instance.IsProfilerActivated || PretendProfilerIsAttached) { DependencyTableStore.Instance.SqlRequestConditionalHolder.Store(sqlRequest, telemetryTuple); } diff --git a/Src/DependencyCollector/Shared/Implementation/DependencyTableStore.cs b/Src/DependencyCollector/Shared/Implementation/DependencyTableStore.cs index fe38c427d..c16fd2dcf 100644 --- a/Src/DependencyCollector/Shared/Implementation/DependencyTableStore.cs +++ b/Src/DependencyCollector/Shared/Implementation/DependencyTableStore.cs @@ -12,6 +12,7 @@ internal class DependencyTableStore : IDisposable internal ObjectInstanceBasedOperationHolder SqlRequestConditionalHolder; internal bool IsProfilerActivated = false; + internal bool IsDesktopHttpDiagnosticSourceActivated = false; private static DependencyTableStore instance; private DependencyTableStore() diff --git a/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs new file mode 100644 index 000000000..a052043c2 --- /dev/null +++ b/Src/DependencyCollector/Shared/Implementation/DesktopDiagnosticSourceHttpProcessing.cs @@ -0,0 +1,81 @@ +#if NET45 +namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation +{ + using System; + using System.Collections.Generic; + using System.Net; + using Microsoft.ApplicationInsights.DataContracts; + using Microsoft.ApplicationInsights.DependencyCollector.Implementation.Operation; + using Microsoft.ApplicationInsights.Extensibility; + using Microsoft.ApplicationInsights.Web.Implementation; + + /// + /// Concrete class with all processing logic to generate RDD data from the callbacks received from HttpDesktopDiagnosticSourceListener. + /// + internal sealed class DesktopDiagnosticSourceHttpProcessing : HttpProcessing + { + internal ObjectInstanceBasedOperationHolder TelemetryTable; + + internal DesktopDiagnosticSourceHttpProcessing(TelemetryConfiguration configuration, ObjectInstanceBasedOperationHolder telemetryTupleHolder, bool setCorrelationHeaders, ICollection correlationDomainExclusionList, string appIdEndpoint) + : base(configuration, SdkVersionUtils.GetSdkVersion("rdd" + RddSource.FrameworkAndDiagnostic + ":"), null, setCorrelationHeaders, correlationDomainExclusionList, appIdEndpoint) + { + if (telemetryTupleHolder == null) + { + throw new ArgumentNullException("telemetryTupleHolder"); + } + + this.TelemetryTable = telemetryTupleHolder; + } + + /// + /// On request send callback from Http diagnostic source. + /// + /// The WebRequest object. + public void OnRequestSend(WebRequest request) + { + this.OnBegin(request, true); + } + + /// + /// On request send callback from Http diagnostic source. + /// + /// The WebRequest object. + /// The WebResponse object. + public void OnResponseReceive(WebRequest request, HttpWebResponse response) + { + this.OnEnd(null, request, response); + } + + /// + /// Implemented by the derived class for adding the tuple to its specific cache. + /// + /// The request which acts the key. + /// The dependency telemetry for the tuple. + /// Boolean value that tells if the current telemetry item is being added by the customer or not. + protected override void AddTupleForWebDependencies(WebRequest webRequest, DependencyTelemetry telemetry, bool isCustomCreated) + { + var telemetryTuple = new Tuple(telemetry, isCustomCreated); + this.TelemetryTable.Store(webRequest, telemetryTuple); + } + + /// + /// Implemented by the derived class for getting the tuple from its specific cache. + /// + /// The request which acts as the key. + /// The tuple for the given request. + protected override Tuple GetTupleForWebDependencies(WebRequest webRequest) + { + return this.TelemetryTable.Get(webRequest); + } + + /// + /// Implemented by the derived class for removing the tuple from its specific cache. + /// + /// The request which acts as the key. + protected override void RemoveTupleForWebDependencies(WebRequest webRequest) + { + this.TelemetryTable.Remove(webRequest); + } + } +} +#endif \ No newline at end of file diff --git a/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs index 93e8ccaa4..f302b3642 100644 --- a/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs +++ b/Src/DependencyCollector/Shared/Implementation/FrameworkHttpProcessing.cs @@ -12,15 +12,7 @@ namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation using Microsoft.ApplicationInsights.Web.Implementation; /// - /// Concrete class with all processing logic to generate RDD data from the callbacks - /// received from framework events for HTTP. This class receives and uses two types of events, which - /// are EventSource from FrameworkHttpEventListener, and DiagnosticSource from - /// HttpDiagnosticSourceListener. The challenge is, the diagnostic source events have the WebRequest - /// object which we need for header injection, but the events can be fired multiple times. So you won't - /// know if it's the first request, or if it's the last response. The event source events fire at the right - /// locations and just once, but they don't have the rich information. This class coordinates both events, - /// store information in DependencyTelemetry properly, and fire the telemetry only on the EventSource - /// response receive to guarantee it's done just once and at the right time. + /// Concrete class with all processing logic to generate RDD data from the callbacks received from FrameworkHttpEventListener. /// internal sealed class FrameworkHttpProcessing : HttpProcessing { @@ -49,6 +41,12 @@ public void OnBeginHttpCallback(long id, string resourceName) try { DependencyCollectorEventSource.Log.BeginCallbackCalled(id, resourceName); + if (DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated) + { + // request is handled by Desktop DiagnosticSource Listener + DependencyCollectorEventSource.Log.TrackingAnExistingTelemetryItemVerbose(); + return; + } if (string.IsNullOrEmpty(resourceName)) { @@ -111,7 +109,6 @@ public void OnBeginHttpCallback(long id, string resourceName) public void OnEndHttpCallback(long id, bool? success, bool synchronous, int? statusCode) { DependencyCollectorEventSource.Log.EndCallbackCalled(id.ToString(CultureInfo.InvariantCulture)); - var telemetryTuple = this.TelemetryTable.Get(id); if (telemetryTuple == null) @@ -125,13 +122,6 @@ public void OnEndHttpCallback(long id, bool? success, bool synchronous, int? sta this.TelemetryTable.Remove(id); DependencyTelemetry telemetry = telemetryTuple.Item1; - // If this telemetry was processed via the DiagnosticSource path, we should record that fact in the - // SdkVersion field - if (this.HasTouchedByDiagnosticSource(telemetry)) - { - telemetry.Context.GetInternalContext().SdkVersion = SdkVersionUtils.GetSdkVersion("rdd" + RddSource.FrameworkAndDiagnostic + ":"); - } - if (statusCode.HasValue) { // We calculate success on the base of http code and do not use the 'success' method argument @@ -152,44 +142,6 @@ public void OnEndHttpCallback(long id, bool? success, bool synchronous, int? sta } } - /// - /// On request send callback from Http diagnostic source. - /// - /// The WebRequest object. - public void OnRequestSend(WebRequest request) - { - // At this point, we need to determine if this is the first time we are examining this request. - // There are 3 possibilities - // 1. This is the very first time - // 2. This is the first time via OnRequestSend, but it's been processed by OnBeginHttpCallback already - // 3. This is not the first time it's processed by OnRequestSend. - // We need to determine which case. If the telemetry object is not found, then it's case 1. If the - // telemetry object exists, but it's never processed via DiagnosticSource, then it's case 2. - // Otherwise, it's case 3. In both case 1 and 2, we need OnBegin to add all properties. - Tuple tuple = this.GetTupleForWebDependencies(request); - DependencyTelemetry telemetry = tuple?.Item1; - - if (this.HasTouchedByDiagnosticSource(telemetry)) - { - // This is case 3, so make sure we skip update if it already exists. - this.OnBegin(request, true /*skipIfNotNew*/); - } - else - { - this.OnBegin(request, false /*skipIfNotNew*/); - } - } - - /// - /// On request send callback from Http diagnostic source. - /// - /// The WebRequest object. - /// The WebResponse object. - public void OnResponseReceive(WebRequest request, HttpWebResponse response) - { - this.OnEnd(null, request, response, false); - } - /// /// Implemented by the derived class for adding the tuple to its specific cache. /// @@ -220,23 +172,6 @@ protected override void RemoveTupleForWebDependencies(WebRequest webRequest) { this.TelemetryTable.Remove(ClientServerDependencyTracker.GetIdForRequestObject(webRequest)); } - - /// - /// Detects if the telemetry object has been processed via the DiagnosticSource path. - /// - /// The DependencyTelemetry object to examine. - private bool HasTouchedByDiagnosticSource(DependencyTelemetry telemetry) - { - // If it was ever processed via the DiagnosticSource path, then telemetry.Name - // must have the HTTP method name at the front, so first character is not a '/'. - string name = telemetry?.Name; - if (!string.IsNullOrEmpty(name) && !name.StartsWith("/", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return false; - } } } #endif \ No newline at end of file diff --git a/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceListener.cs b/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceListener.cs index 84df22d90..b053fbdf8 100644 --- a/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceListener.cs +++ b/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceListener.cs @@ -1,4 +1,4 @@ -#if !NET40 +#if NET45 namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation { using System; @@ -10,14 +10,14 @@ namespace Microsoft.ApplicationInsights.DependencyCollector.Implementation /// internal class HttpDesktopDiagnosticSourceListener : IObserver>, IDisposable { - private readonly FrameworkHttpProcessing httpProcessingFramework; + private readonly DesktopDiagnosticSourceHttpProcessing httpProcessingFramework; private readonly HttpDesktopDiagnosticSourceSubscriber subscribeHelper; private readonly PropertyFetcher requestFetcherRequestEvent; private readonly PropertyFetcher requestFetcherResponseEvent; private readonly PropertyFetcher responseFetcher; private bool disposed = false; - internal HttpDesktopDiagnosticSourceListener(FrameworkHttpProcessing httpProcessing) + internal HttpDesktopDiagnosticSourceListener(DesktopDiagnosticSourceHttpProcessing httpProcessing) { this.httpProcessingFramework = httpProcessing; this.subscribeHelper = new HttpDesktopDiagnosticSourceSubscriber(this); diff --git a/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceSubscriber.cs b/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceSubscriber.cs index 9e044e11f..37dc4d15b 100644 --- a/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceSubscriber.cs +++ b/Src/DependencyCollector/Shared/Implementation/HttpDesktopDiagnosticSourceSubscriber.cs @@ -48,6 +48,7 @@ public void OnNext(DiagnosticListener value) if (value.Name == "System.Net.Http.Desktop") { this.sourceSubscription = value.Subscribe(this.parent, (Predicate)null); + DependencyTableStore.Instance.IsDesktopHttpDiagnosticSourceActivated = true; } } } diff --git a/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs index eabe369ad..e9b8eede5 100644 --- a/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs +++ b/Src/DependencyCollector/Shared/Implementation/HttpProcessing.cs @@ -249,8 +249,7 @@ internal object OnBegin(object thisObj, bool skipIfNotNew) /// The exception object if any. /// This object. /// Return value of the function if any. - /// Whether this method should end the tracking or not. - internal void OnEnd(object exception, object thisObj, object returnValue, bool endTracking) + internal void OnEnd(object exception, object thisObj, object returnValue) { try { @@ -284,12 +283,7 @@ internal void OnEnd(object exception, object thisObj, object returnValue, bool e // Not custom created if (!telemetryTuple.Item2) { - // Remove it from the tracker only if the caller thinks tracking should end here. If not, the caller - // may want to end tracking outside of this function - if (endTracking) - { - this.RemoveTupleForWebDependencies(webRequest); - } + this.RemoveTupleForWebDependencies(webRequest); DependencyTelemetry telemetry = telemetryTuple.Item1; @@ -385,10 +379,7 @@ internal void OnEnd(object exception, object thisObj, object returnValue, bool e } } - if (endTracking) - { - ClientServerDependencyTracker.EndTracking(this.telemetryClient, telemetry); - } + ClientServerDependencyTracker.EndTracking(this.telemetryClient, telemetry); } } catch (Exception ex) diff --git a/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs b/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs index d32113ca7..ae7dfd82a 100644 --- a/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs +++ b/Src/DependencyCollector/Shared/Implementation/ProfilerHttpProcessing.cs @@ -63,7 +63,7 @@ public object OnBeginForGetResponse(object thisObj) /// The resulting return value. public object OnEndForGetResponse(object context, object returnValue, object thisObj) { - this.OnEnd(null, thisObj, returnValue, true); + this.OnEnd(null, thisObj, returnValue); return returnValue; } @@ -75,7 +75,7 @@ public object OnEndForGetResponse(object context, object returnValue, object thi /// This object. public void OnExceptionForGetResponse(object context, object exception, object thisObj) { - this.OnEnd(exception, thisObj, null, true); + this.OnEnd(exception, thisObj, null); } /// @@ -99,7 +99,7 @@ public object OnBeginForGetRequestStream(object thisObj, object transportContext /// The transport context parameter. public void OnExceptionForGetRequestStream(object context, object exception, object thisObj, object transportContext) { - this.OnEnd(exception, thisObj, null, true); + this.OnEnd(exception, thisObj, null); } /// @@ -124,7 +124,7 @@ public object OnBeginForBeginGetResponse(object thisObj, object callback, object /// The return value passed. public object OnEndForEndGetResponse(object context, object returnValue, object thisObj, object asyncResult) { - this.OnEnd(null, thisObj, returnValue, true); + this.OnEnd(null, thisObj, returnValue); return returnValue; } @@ -137,7 +137,7 @@ public object OnEndForEndGetResponse(object context, object returnValue, object /// The asyncResult parameter. public void OnExceptionForEndGetResponse(object context, object exception, object thisObj, object asyncResult) { - this.OnEnd(exception, thisObj, null, true); + this.OnEnd(exception, thisObj, null); } /// @@ -163,7 +163,7 @@ public object OnBeginForBeginGetRequestStream(object thisObj, object callback, o /// The transportContext parameter. public void OnExceptionForEndGetRequestStream(object context, object exception, object thisObj, object asyncResult, object transportContext) { - this.OnEnd(exception, thisObj, null, true); + this.OnEnd(exception, thisObj, null); } #endregion // Http callbacks