Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Changelog
# Changelog

## VNext
- [Add RoleName as a header with Ping Reuests to QuickPulse.](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2113)
- [QuickPulseTelemetryModule takes hints from the service regarding the endpoint to ping and how often to ping it](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2120)

## Version 2.16.0
- [QuickPulseTelemetryModule and MonitoringDataPoint have a new Cloud Role Name field for sending with ping and post requests to QuickPulse Service.](https://github.com/microsoft/ApplicationInsights-dotnet/pull/2100)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Json;

using System.Threading;
using Microsoft.ApplicationInsights.Extensibility.Filtering;
using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
using Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.Implementation.QuickPulse.Helpers;
Expand Down Expand Up @@ -44,6 +44,8 @@ internal sealed class QuickPulseServiceClient : IQuickPulseServiceClient

private readonly Dictionary<string, string> authOpaqueHeaderValues = new Dictionary<string, string>(StringComparer.Ordinal);

private Uri currentServiceUri;

public QuickPulseServiceClient(
Uri serviceUri,
string instanceName,
Expand All @@ -56,7 +58,7 @@ public QuickPulseServiceClient(
int processorCount,
TimeSpan? timeout = null)
{
this.ServiceUri = serviceUri;
this.CurrentServiceUri = serviceUri;
this.instanceName = instanceName;
this.roleName = roleName;
this.streamId = streamId;
Expand All @@ -73,19 +75,24 @@ public QuickPulseServiceClient(
}
}

public Uri ServiceUri { get; }
public Uri CurrentServiceUri
{
get => Volatile.Read(ref this.currentServiceUri);
private set => Volatile.Write(ref this.currentServiceUri, value);
}

public bool? Ping(
string instrumentationKey,
DateTimeOffset timestamp,
string configurationETag,
string authApiKey,
out CollectionConfigurationInfo configurationInfo)
out CollectionConfigurationInfo configurationInfo,
out TimeSpan? servicePollingIntervalHint)
{
var requestUri = string.Format(
CultureInfo.InvariantCulture,
"{0}/ping?ikey={1}",
this.ServiceUri.AbsoluteUri.TrimEnd('/'),
this.CurrentServiceUri.AbsoluteUri.TrimEnd('/'),
Uri.EscapeUriString(instrumentationKey));

return this.SendRequest(
Expand All @@ -94,6 +101,7 @@ public QuickPulseServiceClient(
configurationETag,
authApiKey,
out configurationInfo,
out servicePollingIntervalHint,
requestStream => this.WritePingData(timestamp, requestStream));
}

Expand All @@ -108,7 +116,7 @@ public QuickPulseServiceClient(
var requestUri = string.Format(
CultureInfo.InvariantCulture,
"{0}/post?ikey={1}",
this.ServiceUri.AbsoluteUri.TrimEnd('/'),
this.CurrentServiceUri.AbsoluteUri.TrimEnd('/'),
Uri.EscapeUriString(instrumentationKey));

return this.SendRequest(
Expand All @@ -117,6 +125,7 @@ public QuickPulseServiceClient(
configurationETag,
authApiKey,
out configurationInfo,
out _,
requestStream => this.WriteSamples(samples, instrumentationKey, requestStream, collectionConfigurationErrors));
}

Expand All @@ -130,6 +139,7 @@ public void Dispose()
string configurationETag,
string authApiKey,
out CollectionConfigurationInfo configurationInfo,
out TimeSpan? servicePollingIntervalHint,
Action<Stream> onWriteRequestBody)
{
try
Expand All @@ -149,10 +159,11 @@ public void Dispose()
if (response == null)
{
configurationInfo = null;
servicePollingIntervalHint = null;
return null;
}

return this.ProcessResponse(response, configurationETag, out configurationInfo);
return this.ProcessResponse(response, configurationETag, out configurationInfo, out servicePollingIntervalHint);
}
}
}
Expand All @@ -162,13 +173,15 @@ public void Dispose()
}

configurationInfo = null;
servicePollingIntervalHint = null;
return null;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is known to perform safely on Stream and StreamReader types.")]
private bool? ProcessResponse(HttpWebResponse response, string configurationETag, out CollectionConfigurationInfo configurationInfo)
private bool? ProcessResponse(HttpWebResponse response, string configurationETag, out CollectionConfigurationInfo configurationInfo, out TimeSpan? servicePollingIntervalHint)
{
configurationInfo = null;
servicePollingIntervalHint = null;

bool isSubscribed;
if (!bool.TryParse(response.GetResponseHeader(QuickPulseConstants.XMsQpsSubscribedHeaderName), out isSubscribed))
Expand Down Expand Up @@ -201,6 +214,16 @@ public void Dispose()

string configurationETagHeaderValue = response.GetResponseHeader(QuickPulseConstants.XMsQpsConfigurationETagHeaderName);

if (long.TryParse(response.GetResponseHeader(QuickPulseConstants.XMsQpsServicePollingIntervalHintHeaderName), out long servicePollingIntervalHintInMs))
{
servicePollingIntervalHint = TimeSpan.FromMilliseconds(servicePollingIntervalHintInMs);
}

if (Uri.TryCreate(response.GetResponseHeader(QuickPulseConstants.XMsQpsServiceEndpointRedirectHeaderName), UriKind.Absolute, out Uri serviceEndpointRedirect))
{
this.CurrentServiceUri = serviceEndpointRedirect;
}

try
{
using (Stream responseStream = response.GetResponseStream())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ internal sealed class QuickPulseServiceClient : IQuickPulseServiceClient

private readonly HttpClient httpClient = new HttpClient();

private Uri currentServiceUri;

public QuickPulseServiceClient(
Uri serviceUri,
string instanceName,
Expand All @@ -62,7 +64,7 @@ public QuickPulseServiceClient(
int processorCount,
TimeSpan? timeout = null)
{
this.ServiceUri = serviceUri;
this.CurrentServiceUri = serviceUri;
this.instanceName = instanceName;
this.roleName = roleName;
this.streamId = streamId;
Expand All @@ -79,19 +81,24 @@ public QuickPulseServiceClient(
}
}

public Uri ServiceUri { get; }
public Uri CurrentServiceUri
{
get => Volatile.Read(ref this.currentServiceUri);
private set => Volatile.Write(ref this.currentServiceUri, value);
}

public bool? Ping(
string instrumentationKey,
DateTimeOffset timestamp,
string configurationETag,
string authApiKey,
out CollectionConfigurationInfo configurationInfo)
out CollectionConfigurationInfo configurationInfo,
out TimeSpan? servicePollingIntervalHint)
{
var requestUri = string.Format(
CultureInfo.InvariantCulture,
"{0}/ping?ikey={1}",
this.ServiceUri.AbsoluteUri.TrimEnd('/'),
this.CurrentServiceUri.AbsoluteUri.TrimEnd('/'),
Uri.EscapeUriString(instrumentationKey));

return this.SendRequest(
Expand All @@ -100,6 +107,7 @@ public QuickPulseServiceClient(
configurationETag,
authApiKey,
out configurationInfo,
out servicePollingIntervalHint,
requestStream => this.WritePingData(timestamp, requestStream));
}

Expand All @@ -114,7 +122,7 @@ public QuickPulseServiceClient(
var requestUri = string.Format(
CultureInfo.InvariantCulture,
"{0}/post?ikey={1}",
this.ServiceUri.AbsoluteUri.TrimEnd('/'),
this.CurrentServiceUri.AbsoluteUri.TrimEnd('/'),
Uri.EscapeUriString(instrumentationKey));

return this.SendRequest(
Expand All @@ -123,6 +131,7 @@ public QuickPulseServiceClient(
configurationETag,
authApiKey,
out configurationInfo,
out _,
requestStream => this.WriteSamples(samples, instrumentationKey, requestStream, collectionConfigurationErrors));
}

Expand All @@ -138,6 +147,7 @@ public void Dispose()
string configurationETag,
string authApiKey,
out CollectionConfigurationInfo configurationInfo,
out TimeSpan? servicePollingIntervalHint,
Action<Stream> onWriteRequestBody)
{
try
Expand All @@ -159,10 +169,11 @@ public void Dispose()
if (response == null)
{
configurationInfo = null;
servicePollingIntervalHint = null;
return null;
}

return this.ProcessResponse(response, configurationETag, out configurationInfo);
return this.ProcessResponse(response, configurationETag, out configurationInfo, out servicePollingIntervalHint);
}
}
}
Expand All @@ -173,13 +184,15 @@ public void Dispose()
}

configurationInfo = null;
servicePollingIntervalHint = null;
return null;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "Dispose is known to perform safely on Stream and StreamReader types.")]
private bool? ProcessResponse(HttpResponseMessage response, string configurationETag, out CollectionConfigurationInfo configurationInfo)
private bool? ProcessResponse(HttpResponseMessage response, string configurationETag, out CollectionConfigurationInfo configurationInfo, out TimeSpan? servicePollingIntervalHint)
{
configurationInfo = null;
servicePollingIntervalHint = null;

if (!bool.TryParse(response.Headers.GetValueSafe(QuickPulseConstants.XMsQpsSubscribedHeaderName), out bool isSubscribed))
{
Expand All @@ -205,6 +218,16 @@ public void Dispose()

string configurationETagHeaderValue = response.Headers.GetValueSafe(QuickPulseConstants.XMsQpsConfigurationETagHeaderName);

if (long.TryParse(response.Headers.GetValueSafe(QuickPulseConstants.XMsQpsServicePollingIntervalHintHeaderName), out long servicePollingIntervalHintInMs))
{
servicePollingIntervalHint = TimeSpan.FromMilliseconds(servicePollingIntervalHintInMs);
}

if (Uri.TryCreate(response.Headers.GetValueSafe(QuickPulseConstants.XMsQpsServiceEndpointRedirectHeaderName), UriKind.Absolute, out Uri serviceEndpointRedirect))
{
this.CurrentServiceUri = serviceEndpointRedirect;
}

try
{
using (Stream responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ internal class QuickPulseServiceClientMock : IQuickPulseServiceClient

public CollectionConfigurationError[] CollectionConfigurationErrors { get; private set; }

public TimeSpan? ServicePollingIntervalHint { private get; set; }

public Uri CurrentServiceUriMockValue { private get; set; }

public bool? ReturnValueFromSubmitSample { private get; set; }

public int? LastSampleBatchSize { get; private set; }
Expand All @@ -50,7 +54,7 @@ public List<QuickPulseDataSample> SnappedSamples
}
}

public Uri ServiceUri { get; }
public Uri CurrentServiceUri { get; private set; }

public void Reset()
{
Expand All @@ -70,7 +74,8 @@ public void Reset()
DateTimeOffset timestamp,
string configurationETag,
string authApiKey,
out CollectionConfigurationInfo configurationInfo)
out CollectionConfigurationInfo configurationInfo,
out TimeSpan? servicePollingIntervalHint)
{
lock (this.ResponseLock)
{
Expand All @@ -90,6 +95,8 @@ public void Reset()
}

configurationInfo = this.CollectionConfigurationInfo?.ETag == configurationETag ? null : this.CollectionConfigurationInfo;
servicePollingIntervalHint = this.ServicePollingIntervalHint;
this.CurrentServiceUri = this.CurrentServiceUriMockValue;

return this.ReturnValueFromPing;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public void QuickPulseCollectionStateManagerInitiallyInIdleState()
() => { },
() => null,
_ => { },
_ => null);
_ => null,
_ => { });

// ACT

Expand Down Expand Up @@ -679,6 +680,40 @@ public void QuickPulseCollectionStateManagerReportsErrorsInCollectionConfigurati
Assert.AreEqual(Predicate.Equal.ToString(), errors[3].Data["FilterPredicate"]);
Assert.AreEqual("Request1", errors[3].Data["FilterComparand"]);
}

[TestMethod]
public void QuickPulseCollectionStateManagerRespectsServicePollingIntervalHint()
{
// ARRANGE
var timings = QuickPulseTimings.Default;
var serviceClient = new QuickPulseServiceClientMock { ReturnValueFromPing = false, ReturnValueFromSubmitSample = false };
var actions = new List<string>();
var returnedSamples = new List<QuickPulseDataSample>();
var timeProvider = new ClockMock();
var manager = CreateManager(serviceClient, timeProvider, actions, returnedSamples, timings);
TimeSpan intervalHint1 = TimeSpan.FromSeconds(65);
TimeSpan intervalHint2 = TimeSpan.FromSeconds(75);

// ACT
serviceClient.ReturnValueFromPing = false;

TimeSpan oldServicePollingInterval = manager.UpdateState("ikey1", string.Empty);

serviceClient.ServicePollingIntervalHint = intervalHint1;
TimeSpan newServicePollingInterval1 = manager.UpdateState("ikey1", string.Empty);

serviceClient.ServicePollingIntervalHint = intervalHint2;
TimeSpan newServicePollingInterval2 = manager.UpdateState("ikey1", string.Empty);

serviceClient.ServicePollingIntervalHint = null;
TimeSpan newServicePollingInterval3 = manager.UpdateState("ikey1", string.Empty);

// ASSERT
Assert.AreEqual(timings.ServicePollingInterval, oldServicePollingInterval);
Assert.AreEqual(intervalHint1, newServicePollingInterval1);
Assert.AreEqual(intervalHint2, newServicePollingInterval2);
Assert.AreEqual(intervalHint2, newServicePollingInterval3);
}

#region Helpers

Expand Down Expand Up @@ -730,7 +765,8 @@ private static QuickPulseCollectionStateManager CreateManager(
CollectionConfigurationError[] errors;
new CollectionConfiguration(collectionConfigurationInfo, out errors, timeProvider);
return errors;
});
},
_ => { });

return manager;
}
Expand Down
Loading