Skip to content

Commit

Permalink
Endpoint and ping interval hints for QuickPulseModule. (#2120)
Browse files Browse the repository at this point in the history
Endpoint and ping interval hints for QuickPulseModule.
  • Loading branch information
tokaplan authored Dec 2, 2020
1 parent 14de4d1 commit 375d62e
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 68 deletions.
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

0 comments on commit 375d62e

Please sign in to comment.