diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f69847b59..7cb5064c36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
This changelog will be used to generate documentation on [release notes page](http://azure.microsoft.com/en-us/documentation/articles/app-insights-release-notes-dotnet/).
+## Version 2.3.0-beta1
+- Added metric aggregation functionality via MetricManager and Metric classes.
+- Exposed a source field on RequestTelemetry. This can be used to store a representation of the component that issued the incoming http request.
+
## Version 2.2.0
- Includes all changes since 2.1.0 stable release.
diff --git a/GlobalStaticVersion.props b/GlobalStaticVersion.props
index f5fb43f0aa..7fd2fea53c 100644
--- a/GlobalStaticVersion.props
+++ b/GlobalStaticVersion.props
@@ -6,14 +6,14 @@
Update for every public release.
-->
2
- 2
+ 3
0
-
+ beta1
- 2016-06-02
+ 2016-11-11
.PreReleaseVersion
$(MSBuildThisFileDirectory)$(PreReleaseVersionFileName)
diff --git a/Test/CoreSDK.Test/Net46/Extensibility/Implementation/RichPayloadEventSourceTest.cs b/Test/CoreSDK.Test/Net46/Extensibility/Implementation/RichPayloadEventSourceTest.cs
index 746def208e..d1d944cb5c 100644
--- a/Test/CoreSDK.Test/Net46/Extensibility/Implementation/RichPayloadEventSourceTest.cs
+++ b/Test/CoreSDK.Test/Net46/Extensibility/Implementation/RichPayloadEventSourceTest.cs
@@ -84,9 +84,11 @@ public void RichPayloadEventSourceMetricSentTest()
{
this.DoTracking(
RichPayloadEventSource.Keywords.Metrics,
+#pragma warning disable CS0618
new MetricTelemetry("TestMetric", 1),
typeof(External.MetricData),
(client, item) => { client.TrackMetric((MetricTelemetry)item); });
+#pragma warning restore CS0618
}
///
diff --git a/Test/CoreSDK.Test/Shared/Core.Shared.Tests.projitems b/Test/CoreSDK.Test/Shared/Core.Shared.Tests.projitems
index d722eba6ea..6905615c8e 100644
--- a/Test/CoreSDK.Test/Shared/Core.Shared.Tests.projitems
+++ b/Test/CoreSDK.Test/Shared/Core.Shared.Tests.projitems
@@ -27,10 +27,14 @@
+
+
+
+
@@ -65,6 +69,7 @@
+
diff --git a/Test/CoreSDK.Test/Shared/DataContracts/MetricTelemetryTest.cs b/Test/CoreSDK.Test/Shared/DataContracts/MetricTelemetryTest.cs
index f0c0519fd4..72b6621882 100644
--- a/Test/CoreSDK.Test/Shared/DataContracts/MetricTelemetryTest.cs
+++ b/Test/CoreSDK.Test/Shared/DataContracts/MetricTelemetryTest.cs
@@ -7,7 +7,6 @@
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Assert = Xunit.Assert;
-
[TestClass]
public class MetricTelemetryTest
@@ -32,18 +31,24 @@ public void EventTelemetryReturnsNonNullContext()
Assert.NotNull(item.Context);
}
+#pragma warning disable CS0618
[TestMethod]
public void MetricTelemetrySuppliesConstructorThatTakesNameAndValueToSimplifyAdvancedScenarios()
{
var instance = new MetricTelemetry("Test Metric", 4.2);
+
Assert.Equal("Test Metric", instance.Name);
Assert.Equal(4.2, instance.Value);
}
+#pragma warning restore CS0618
[TestMethod]
public void MetricTelemetrySuppliesPropertiesForCustomerToSendAggregatedMetric()
{
+#pragma warning disable CS0618
var instance = new MetricTelemetry("Test Metric", 4.2);
+#pragma warning restore CS0618
+
instance.Count = 5;
instance.Min = 1.2;
instance.Max = 6.4;
@@ -54,38 +59,15 @@ public void MetricTelemetrySuppliesPropertiesForCustomerToSendAggregatedMetric()
Assert.Equal(0.5, instance.StandardDeviation);
}
- [TestMethod]
- public void MeasurementMetricTelemetrySerializesToJsonCorrectly()
- {
- var expected = new MetricTelemetry();
- expected.Name = "My Page";
- expected.Value = 42;
- expected.Properties.Add("Property1", "Value1");
-
- var item = TelemetryItemTestHelper.SerializeDeserializeTelemetryItem(expected);
-
- // NOTE: It's correct that we use the v1 name here, and therefore we test against it.
- Assert.Equal(item.name, AI.ItemType.Metric);
-
- Assert.Equal(typeof(AI.MetricData).Name, item.data.baseType);
- Assert.Equal(2, item.data.baseData.ver);
- Assert.Equal(1, item.data.baseData.metrics.Count);
- Assert.Equal(expected.Name, item.data.baseData.metrics[0].name);
- Assert.Equal(AI.DataPointType.Measurement, item.data.baseData.metrics[0].kind);
- Assert.Equal(expected.Value, item.data.baseData.metrics[0].value);
- Assert.False(item.data.baseData.metrics[0].count.HasValue);
- Assert.False(item.data.baseData.metrics[0].min.HasValue);
- Assert.False(item.data.baseData.metrics[0].max.HasValue);
- Assert.False(item.data.baseData.metrics[0].stdDev.HasValue);
- Assert.Equal(expected.Properties.ToArray(), item.data.baseData.properties.ToArray());
- }
-
[TestMethod]
public void AggregateMetricTelemetrySerializesToJsonCorrectly()
{
var expected = new MetricTelemetry();
+
expected.Name = "My Page";
+#pragma warning disable CS0618
expected.Value = 42;
+#pragma warning restore CS0618
expected.Count = 5;
expected.Min = 1.2;
expected.Max = 6.4;
@@ -100,7 +82,9 @@ public void AggregateMetricTelemetrySerializesToJsonCorrectly()
Assert.Equal(1, item.data.baseData.metrics.Count);
Assert.Equal(expected.Name, item.data.baseData.metrics[0].name);
Assert.Equal(AI.DataPointType.Aggregation, item.data.baseData.metrics[0].kind);
+#pragma warning disable CS0618
Assert.Equal(expected.Value, item.data.baseData.metrics[0].value);
+#pragma warning restore CS0618
Assert.Equal(expected.Count.Value, item.data.baseData.metrics[0].count.Value);
Assert.Equal(expected.Min.Value, item.data.baseData.metrics[0].min.Value);
Assert.Equal(expected.Max.Value, item.data.baseData.metrics[0].max.Value);
@@ -109,6 +93,45 @@ public void AggregateMetricTelemetrySerializesToJsonCorrectly()
Assert.Equal(expected.Properties.ToArray(), item.data.baseData.properties.ToArray());
}
+ [TestMethod]
+ public void MetricTelemetrySuppliesConstructorThatAllowsToFullyPopulateAggregationData()
+ {
+ var instance = new MetricTelemetry(
+ name: "Test Metric",
+ count: 4,
+ sum: 40,
+ min: 5,
+ max: 15,
+ standardDeviation: 4.2);
+
+ Assert.Equal("Test Metric", instance.Name);
+ Assert.Equal(4, instance.Count);
+ Assert.Equal(40, instance.Sum);
+ Assert.Equal(5, instance.Min);
+ Assert.Equal(15, instance.Max);
+ Assert.Equal(4.2, instance.StandardDeviation);
+ }
+
+ [TestMethod]
+ public void MetricTelemetrySuppliesPropertiesForCustomerToSendAggregionData()
+ {
+ var instance = new MetricTelemetry();
+
+ instance.Name = "Test Metric";
+ instance.Count = 4;
+ instance.Sum = 40;
+ instance.Min = 5.0;
+ instance.Max = 15.0;
+ instance.StandardDeviation = 4.2;
+
+ Assert.Equal("Test Metric", instance.Name);
+ Assert.Equal(4, instance.Count);
+ Assert.Equal(40, instance.Sum);
+ Assert.Equal(5, instance.Min);
+ Assert.Equal(15, instance.Max);
+ Assert.Equal(4.2, instance.StandardDeviation);
+ }
+
[TestMethod]
public void MetricTelemetrySerializesStructuredIKeyCorrectlyPreservingCaseOfPrefix()
{
@@ -165,17 +188,19 @@ public void SerializeWritesNullValuesAsExpectedByEndpoint()
Assert.Equal(2, item.data.baseData.ver);
}
+#pragma warning disable CS0618
[TestMethod]
- public void SerializeReplacesNaNValueOn0()
+ public void SanitizeReplacesNaNValueOn0()
{
MetricTelemetry original = new MetricTelemetry("test", double.NaN);
((ITelemetry)original).Sanitize();
Assert.Equal(0, original.Value);
}
+#pragma warning restore CS0618
[TestMethod]
- public void SerializeReplacesNaNMinOn0()
+ public void SanitizeReplacesNaNMinOn0()
{
MetricTelemetry original = new MetricTelemetry { Min = double.NaN };
((ITelemetry)original).Sanitize();
@@ -184,7 +209,7 @@ public void SerializeReplacesNaNMinOn0()
}
[TestMethod]
- public void SerializeReplacesNaNMaxOn0()
+ public void SanitizeReplacesNaNMaxOn0()
{
MetricTelemetry original = new MetricTelemetry { Max = double.NaN };
((ITelemetry)original).Sanitize();
@@ -193,12 +218,55 @@ public void SerializeReplacesNaNMaxOn0()
}
[TestMethod]
- public void SerializeReplacesNaNStandardDeviationOn0()
+ public void SanitizeReplacesNaNStandardDeviationOn0()
{
MetricTelemetry original = new MetricTelemetry { StandardDeviation = double.NaN };
((ITelemetry)original).Sanitize();
Assert.Equal(0, original.StandardDeviation.Value);
}
+
+ [TestMethod]
+ public void SanitizeReplacesNaNSumOn0()
+ {
+ MetricTelemetry original = new MetricTelemetry();
+ original.Name = "Test";
+ original.Sum = double.NaN;
+
+ ((ITelemetry)original).Sanitize();
+
+ Assert.Equal(0, original.Sum);
+ }
+
+ [TestMethod]
+ public void SanitizeReplacesNegativeCountOn1()
+ {
+ MetricTelemetry original = new MetricTelemetry();
+ original.Name = "Test";
+ original.Count = -5; ;
+
+ ((ITelemetry)original).Sanitize();
+
+ Assert.Equal(1, original.Count);
+ }
+
+ [TestMethod]
+ public void SanitizeReplacesZeroCountOn1()
+ {
+ MetricTelemetry original = new MetricTelemetry();
+ original.Name = "Test";
+
+ ((ITelemetry)original).Sanitize();
+
+ Assert.Equal(1, original.Count);
+ }
+
+ [TestMethod]
+ public void CountPropertyGetterReturnsOneIfNoValueIsSet()
+ {
+ MetricTelemetry telemetry = new MetricTelemetry();
+
+ Assert.Equal(1, telemetry.Count);
+ }
}
}
diff --git a/Test/CoreSDK.Test/Shared/EnumerableExtensions.cs b/Test/CoreSDK.Test/Shared/EnumerableExtensions.cs
new file mode 100644
index 0000000000..492b83d87a
--- /dev/null
+++ b/Test/CoreSDK.Test/Shared/EnumerableExtensions.cs
@@ -0,0 +1,28 @@
+
+namespace Microsoft.ApplicationInsights
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ internal static class EnumerableExtensions
+ {
+ public static double StdDev(this IEnumerable sequence)
+ {
+ return StdDev(sequence, (e) => e);
+ }
+
+ public static double StdDev(this IEnumerable sequence, Func selector)
+ {
+ if (sequence.Count() <= 0)
+ {
+ return 0;
+ }
+
+ double avg = sequence.Average(selector);
+ double sum = sequence.Sum(e => Math.Pow(selector(e) - avg, 2));
+
+ return Math.Sqrt(sum / sequence.Count());
+ }
+ }
+}
diff --git a/Test/CoreSDK.Test/Shared/Extensibility/MetricManagerTest.cs b/Test/CoreSDK.Test/Shared/Extensibility/MetricManagerTest.cs
new file mode 100644
index 0000000000..a9ce7ddd00
--- /dev/null
+++ b/Test/CoreSDK.Test/Shared/Extensibility/MetricManagerTest.cs
@@ -0,0 +1,271 @@
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using Microsoft.ApplicationInsights.Channel;
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.TestFramework;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Assert = Xunit.Assert;
+
+ [TestClass]
+ public class MetricManagerTest
+ {
+ [TestMethod]
+ public void CanCreateMetricHavingNoDimensions()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+ using (MetricManager manager = new MetricManager(client))
+ {
+ // Act
+ Metric metric = manager.CreateMetric("Test Metric");
+ metric.Track(42);
+ }
+
+ // Assert (single metric aggregation exists in the output)
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal("Test Metric", aggregatedMetric.Name);
+
+ Assert.Equal(1, aggregatedMetric.Count);
+ Assert.Equal(42, aggregatedMetric.Sum);
+
+ // note: interval duration property is auto-generated
+ Assert.Equal(1, aggregatedMetric.Properties.Count);
+ }
+
+ [TestMethod]
+ public void CanCreateMetricExplicitlySettingDimensionsToNull()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ // Act
+ Metric metric = manager.CreateMetric("Test Metric", null);
+ metric.Track(42);
+ }
+
+ // Assert
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal("Test Metric", aggregatedMetric.Name);
+
+ Assert.Equal(1, aggregatedMetric.Count);
+ Assert.Equal(42, aggregatedMetric.Sum);
+
+ // note: interval duration property is auto-generated
+ Assert.Equal(1, aggregatedMetric.Properties.Count);
+ }
+
+ [TestMethod]
+ public void CanCreateMetricWithASetOfDimensions()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+
+ var dimensions = new Dictionary {
+ { "Dim1", "Value1"},
+ { "Dim2", "Value2"}
+ };
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ // Act
+ Metric metric = manager.CreateMetric("Test Metric", dimensions);
+ metric.Track(42);
+ }
+
+ // Assert
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal("Test Metric", aggregatedMetric.Name);
+
+ Assert.Equal(1, aggregatedMetric.Count);
+ Assert.Equal(42, aggregatedMetric.Sum);
+
+ // note: interval duration property is auto-generated
+ Assert.Equal(3, aggregatedMetric.Properties.Count);
+
+ Assert.Equal("Value1", aggregatedMetric.Properties["Dim1"]);
+ Assert.Equal("Value2", aggregatedMetric.Properties["Dim2"]);
+ }
+
+ [TestMethod]
+ public void AggregatedMetricTelemetryHasIntervalDurationProperty()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ metric.Track(42);
+ }
+
+ // Assert
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal("Test Metric", aggregatedMetric.Name);
+
+ Assert.Equal(1, aggregatedMetric.Count);
+ Assert.Equal(1, aggregatedMetric.Properties.Count);
+
+ Assert.True(aggregatedMetric.Properties.ContainsKey("IntervalDurationMs"));
+ }
+
+ [TestMethod]
+ public void AggregatedMetricTelemetryIntervalDurationPropertyIsPositiveInteger()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ metric.Track(42);
+ }
+
+ // Assert
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal("Test Metric", aggregatedMetric.Name);
+
+ Assert.Equal(1, aggregatedMetric.Count);
+ Assert.Equal(1, aggregatedMetric.Properties.Count);
+
+ Assert.True(aggregatedMetric.Properties.ContainsKey("IntervalDurationMs"));
+ Assert.True(long.Parse(aggregatedMetric.Properties["IntervalDurationMs"]) > 0);
+ }
+
+ [TestMethod]
+ public void EqualMetricsAreCombinedIntoSignleAggregatedStatsStructure()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+
+ Metric metric1 = null;
+ Metric metric2 = null;
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ // note: on first go aggregators may be different because manager may
+ // snapshot after first got created but before the second
+ for (int i = 0; i < 2; i++)
+ {
+ metric1 = manager.CreateMetric("Test Metric");
+ metric2 = manager.CreateMetric("Test Metric");
+
+ // Act
+ metric1.Track(10);
+ metric2.Track(5);
+
+ manager.Flush();
+
+ if (sentTelemetry.Count == 1)
+ {
+ break;
+ }
+ else
+ {
+ sentTelemetry.Clear();
+ }
+ }
+ }
+
+ // Assert
+ Assert.Equal(1, sentTelemetry.Count);
+
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal(2, aggregatedMetric.Count);
+ Assert.Equal(15, aggregatedMetric.Sum);
+ }
+
+ [TestMethod]
+ public void CanDisposeMetricManagerMultipleTimes()
+ {
+ MetricManager manager = null;
+
+ using (manager = new MetricManager()) { }
+
+ Assert.DoesNotThrow(() => { manager.Dispose(); });
+ }
+
+ [TestMethod]
+ public void FlushCreatesAggregatedMetricTelemetry()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ metric.Track(42);
+
+ // Act
+ manager.Flush();
+
+ // Assert
+ Assert.Equal(1, sentTelemetry.Count);
+
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+ Assert.NotNull(aggregatedMetric);
+ }
+ }
+
+ [TestMethod]
+ public void DisposingManagerCreatesAggregatedMetricTelemetry()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ metric.Track(42);
+
+ // Act
+ manager.Dispose();
+
+ // Assert
+ Assert.Equal(1, sentTelemetry.Count);
+
+ var aggregatedMetric = (MetricTelemetry)sentTelemetry.Single();
+ Assert.NotNull(aggregatedMetric);
+ }
+ }
+
+ private TelemetryClient InitializeTelemetryClient(List sentTelemetry)
+ {
+ var channel = new StubTelemetryChannel { OnSend = t => sentTelemetry.Add(t) };
+ var telemetryConfiguration = new TelemetryConfiguration { InstrumentationKey = Guid.NewGuid().ToString(), TelemetryChannel = channel };
+
+ var client = new TelemetryClient(telemetryConfiguration);
+
+ return client;
+ }
+ }
+}
diff --git a/Test/CoreSDK.Test/Shared/Extensibility/MetricSample.cs b/Test/CoreSDK.Test/Shared/Extensibility/MetricSample.cs
new file mode 100644
index 0000000000..c2e925bcab
--- /dev/null
+++ b/Test/CoreSDK.Test/Shared/Extensibility/MetricSample.cs
@@ -0,0 +1,15 @@
+
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Generic;
+
+ internal class MetricSample
+ {
+ public string Name { get; set; }
+
+ public IDictionary Dimensions { get; set; }
+
+ public double Value { get; set; }
+ }
+}
diff --git a/Test/CoreSDK.Test/Shared/Extensibility/MetricTest.cs b/Test/CoreSDK.Test/Shared/Extensibility/MetricTest.cs
new file mode 100644
index 0000000000..53c784b510
--- /dev/null
+++ b/Test/CoreSDK.Test/Shared/Extensibility/MetricTest.cs
@@ -0,0 +1,421 @@
+
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+
+ using Microsoft.ApplicationInsights.Channel;
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.TestFramework;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Assert = Xunit.Assert;
+
+ [TestClass]
+ public class MetricTest
+ {
+ [TestMethod]
+ public void MetricInvokesMetricProcessorsForEachValueTracked()
+ {
+ // Arrange
+ var sentTelemetry = new List();
+ var sentSamples = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry, sentSamples);
+
+ var dimensions = new Dictionary {
+ { "Dim1", "Value1"},
+ { "Dim2", "Value2"}
+ };
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric", dimensions);
+
+ // Act
+ metric.Track(42);
+ }
+
+ // Assert
+ var sample = (MetricSample)sentSamples.Single();
+
+ Assert.Equal("Test Metric", sample.Name);
+
+ Assert.Equal(42, sample.Value);
+
+ Assert.Equal("Value1", sample.Dimensions["Dim1"]);
+ Assert.Equal("Value2", sample.Dimensions["Dim2"]);
+ }
+
+ [TestMethod]
+ public void MetricAggregatorCalculatesSampleCountCorrectly()
+ {
+ // Arrange
+ double[] testValues = { 4.45, 8, 29.21, 78.43, 0 };
+
+ var sentTelemetry = new List();
+ var sentSamples = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry, sentSamples);
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ for (int i = 0; i < testValues.Length; i++)
+ {
+ metric.Track(testValues[i]);
+ }
+ }
+
+ // Assert
+ int sentSampleCount = sentTelemetry.Sum(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return (metric == null) || (!metric.Count.HasValue) ? 0 : metric.Count.Value;
+ });
+
+ Assert.Equal(testValues.Length, sentSampleCount);
+ }
+
+ [TestMethod]
+ public void MetricAggregatorCalculatesSumCorrectly()
+ {
+ // Arrange
+ double[] testValues = { 4.45, 8, 29.21, 78.43, 0 };
+
+ var sentTelemetry = new List();
+ var sentSamples = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry, sentSamples);
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ for (int i = 0; i < testValues.Length; i++)
+ {
+ metric.Track(testValues[i]);
+ }
+ }
+
+ // Assert
+ double sentSampleSum = sentTelemetry.Sum(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return metric == null ? 0 : metric.Sum;
+ });
+
+ Assert.Equal(testValues.Sum(), sentSampleSum);
+ }
+
+ [TestMethod]
+ public void MetricAggregatorCalculatesMinCorrectly()
+ {
+ // Arrange
+ double[] testValues = { 4.45, 8, 29.21, 78.43, 1.4 };
+
+ var sentTelemetry = new List();
+ var sentSamples = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry, sentSamples);
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ for (int i = 0; i < testValues.Length; i++)
+ {
+ metric.Track(testValues[i]);
+ }
+ }
+
+ // Assert
+ double sentSampleSum = sentTelemetry.Min(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return (metric == null) || (!metric.Min.HasValue) ? 0 : metric.Min.Value;
+ });
+
+ Assert.Equal(testValues.Min(), sentSampleSum);
+ }
+
+ [TestMethod]
+ public void MetricAggregatorCalculatesMaxCorrectly()
+ {
+ // Arrange
+ double[] testValues = { 4.45, 8, 29.21, 78.43, 1.4 };
+
+ var sentTelemetry = new List();
+ var sentSamples = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry, sentSamples);
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ for (int i = 0; i < testValues.Length; i++)
+ {
+ metric.Track(testValues[i]);
+ }
+ }
+
+ // Assert
+ double sentSampleMax = sentTelemetry.Max(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return (metric == null) || (!metric.Max.HasValue) ? 0 : metric.Max.Value;
+ });
+
+ Assert.Equal(testValues.Max(), sentSampleMax);
+ }
+
+ [TestMethod]
+ public void MetricAggregatorCalculatesStandardDeviationCorrectly()
+ {
+ // Arrange
+ double[] testValues = { 1, 2, 3, 4, 5 };
+
+ var sentTelemetry = new List();
+ var sentSamples = new List();
+
+ var client = this.InitializeTelemetryClient(sentTelemetry, sentSamples);
+
+ using (MetricManager manager = new MetricManager(client))
+ {
+ Metric metric = manager.CreateMetric("Test Metric");
+
+ // Act
+ for (int i = 0; i < testValues.Length; i++)
+ {
+ metric.Track(testValues[i]);
+ }
+ }
+
+ // Assert
+ double sumOfSquares = sentTelemetry.Sum(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return
+ metric == null
+ ? 0
+ : Math.Pow(metric.StandardDeviation.Value, 2) * metric.Count.Value + Math.Pow(metric.Sum, 2) / metric.Count.Value;
+ });
+
+ int count = sentTelemetry.Sum(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return metric == null ? 0 : metric.Count.Value;
+ });
+
+ double sum = sentTelemetry.Sum(
+ (telemetry) => {
+ var metric = telemetry as MetricTelemetry;
+ return metric == null ? 0 : metric.Sum;
+ });
+
+ double stddev = Math.Sqrt(sumOfSquares / count - Math.Pow(sum / count, 2));
+
+ Assert.Equal(testValues.StdDev(), stddev);
+ }
+
+ #region Equitable implementation tests
+
+ [TestMethod]
+ public void MetricNeverEqualsNull()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric");
+ object other = null;
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void MetricEqualsItself()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric");
+
+ Assert.True(metric.Equals(metric));
+ }
+ }
+
+ [TestMethod]
+ public void MetricNotEqualsOtherObject()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric");
+ var other = new object();
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void MetricsAreEqualForTheSameMetricNameWithoutDimensions()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric");
+ Metric other = manager.CreateMetric("My metric");
+
+ Assert.True(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void MetricNameIsCaseSensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric");
+ Metric other = manager.CreateMetric("My Metric");
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void MetricNameIsAccentSensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric");
+ Metric other = manager.CreateMetric("My métric");
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void MetricsAreEqualIfDimensionsSetToNothingImplicitlyAndExplicitly()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric", null);
+ Metric other = manager.CreateMetric("My metric");
+
+ Assert.True(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void MetricsAreEqualIfDimensionsSetToNothingImplicitlyAndExplicitlyAsEmptySet()
+ {
+ using (var manager = new MetricManager())
+ {
+ Metric metric = manager.CreateMetric("My metric", new Dictionary());
+ Metric other = manager.CreateMetric("My metric");
+
+ Assert.True(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void DimensionsAreOrderInsensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ var dimensionSet1 = new Dictionary() {
+ { "Dim1", "Value1"},
+ { "Dim2", "Value2"},
+ };
+
+ var dimensionSet2 = new Dictionary() {
+ { "Dim2", "Value2"},
+ { "Dim1", "Value1"},
+ };
+
+ Metric metric = manager.CreateMetric("My metric", dimensionSet1);
+ Metric other = manager.CreateMetric("My metric", dimensionSet2);
+
+ Assert.True(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void DimensionNamesAreCaseSensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ var dimensionSet1 = new Dictionary() { { "Dim1", "Value1" } };
+ var dimensionSet2 = new Dictionary() { { "dim1", "Value1" } };
+
+ Metric metric = manager.CreateMetric("My metric", dimensionSet1);
+ Metric other = manager.CreateMetric("My metric", dimensionSet2);
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void DimensionNamesAreAccentSensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ var dimensionSet1 = new Dictionary() { { "Dim1", "Value1" } };
+ var dimensionSet2 = new Dictionary() { { "Dím1", "Value1" } };
+
+ Metric metric = manager.CreateMetric("My metric", dimensionSet1);
+ Metric other = manager.CreateMetric("My metric", dimensionSet2);
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void DimensionValuesAreCaseSensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ var dimensionSet1 = new Dictionary() { { "Dim1", "Value1" } };
+ var dimensionSet2 = new Dictionary() { { "Dim1", "value1" } };
+
+ Metric metric = manager.CreateMetric("My metric", dimensionSet1);
+ Metric other = manager.CreateMetric("My metric", dimensionSet2);
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ [TestMethod]
+ public void DimensionValuesAreAccentSensitive()
+ {
+ using (var manager = new MetricManager())
+ {
+ var dimensionSet1 = new Dictionary() { { "Dim1", "Value1" } };
+ var dimensionSet2 = new Dictionary() { { "Dim1", "Válue1" } };
+
+ Metric metric = manager.CreateMetric("My metric", dimensionSet1);
+ Metric other = manager.CreateMetric("My metric", dimensionSet2);
+
+ Assert.False(metric.Equals(other));
+ }
+ }
+
+ #endregion
+
+ private TelemetryClient InitializeTelemetryClient(List sentTelemetry, List sentSamples)
+ {
+ var channel = new StubTelemetryChannel { OnSend = t => sentTelemetry.Add(t) };
+
+ var telemetryConfiguration = new TelemetryConfiguration { InstrumentationKey = Guid.NewGuid().ToString(), TelemetryChannel = channel };
+ telemetryConfiguration.MetricProcessors.Add(new StubMetricProcessor(sentSamples));
+
+ var client = new TelemetryClient(telemetryConfiguration);
+
+ return client;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/Test/CoreSDK.Test/Shared/Extensibility/StubMetricProcessor.cs b/Test/CoreSDK.Test/Shared/Extensibility/StubMetricProcessor.cs
new file mode 100644
index 0000000000..5bc1eb6024
--- /dev/null
+++ b/Test/CoreSDK.Test/Shared/Extensibility/StubMetricProcessor.cs
@@ -0,0 +1,31 @@
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Generic;
+
+ internal class StubMetricProcessor : IMetricProcessor
+ {
+ private IList sampleList;
+
+ public StubMetricProcessor(IList sampleList)
+ {
+ if (sampleList == null)
+ {
+ throw new ArgumentNullException("sampleList");
+ }
+
+ this.sampleList = sampleList;
+ }
+
+ public void Track(Metric metric, double value)
+ {
+ this.sampleList.Add(
+ new MetricSample()
+ {
+ Name = metric.Name,
+ Dimensions = metric.Dimensions,
+ Value = value
+ });
+ }
+ }
+}
diff --git a/Test/CoreSDK.Test/Shared/Extensibility/TelemetryConfigurationTest.cs b/Test/CoreSDK.Test/Shared/Extensibility/TelemetryConfigurationTest.cs
index 5fa6894769..40c4e7d537 100644
--- a/Test/CoreSDK.Test/Shared/Extensibility/TelemetryConfigurationTest.cs
+++ b/Test/CoreSDK.Test/Shared/Extensibility/TelemetryConfigurationTest.cs
@@ -277,6 +277,24 @@ public void TelemetryProcessorsCollectionIsReadOnly()
#endregion
+ #region MetricProcessors
+
+ [TestMethod]
+ public void MetricProcessorsReturnsAnEmptyListByDefaultToAvoidNullReferenceExceptionsInUserCode()
+ {
+ var configuration = new TelemetryConfiguration();
+ Assert.Equal(0, configuration.MetricProcessors.Count);
+ }
+
+ [TestMethod]
+ public void MetricPrcessorsReturnsThreadSafeList()
+ {
+ var configuration = new TelemetryConfiguration();
+ Assert.Equal(typeof(SnapshottingList), configuration.MetricProcessors.GetType());
+ }
+
+ #endregion
+
#region Serialized Configuration
[TestMethod]
public void TelemetryConfigThrowsIfSerializedConfigIsNull()
diff --git a/Test/CoreSDK.Test/Shared/TelemetryClientTest.cs b/Test/CoreSDK.Test/Shared/TelemetryClientTest.cs
index 58b8ad2ea9..78c17263c2 100644
--- a/Test/CoreSDK.Test/Shared/TelemetryClientTest.cs
+++ b/Test/CoreSDK.Test/Shared/TelemetryClientTest.cs
@@ -143,17 +143,50 @@ public void InitializeDoesNotOverrideNodeName()
#region TrackMetric
+ [TestMethod]
+ public void TrackMetricSendsSpecifiedAggregatedMetricTelemetry()
+ {
+ var sentTelemetry = new List();
+ var client = this.InitializeTelemetryClient(sentTelemetry);
+
+ client.TrackMetric(
+ new MetricTelemetry()
+ {
+ Name = "Test Metric",
+ Count = 5,
+ Sum = 40,
+ Min = 3.0,
+ Max = 4.0,
+ StandardDeviation = 1.0
+ });
+
+ var metric = (MetricTelemetry)sentTelemetry.Single();
+
+ Assert.Equal("Test Metric", metric.Name);
+ Assert.Equal(5, metric.Count);
+ Assert.Equal(40, metric.Sum);
+ Assert.Equal(3.0, metric.Min);
+ Assert.Equal(4.0, metric.Max);
+ Assert.Equal(1.0, metric.StandardDeviation);
+ }
+
[TestMethod]
public void TrackMetricSendsMetricTelemetryWithSpecifiedNameAndValue()
{
var sentTelemetry = new List();
var client = this.InitializeTelemetryClient(sentTelemetry);
+#pragma warning disable CS0618
client.TrackMetric("TestMetric", 42);
+#pragma warning restore CS0618
var metric = (MetricTelemetry)sentTelemetry.Single();
+
Assert.Equal("TestMetric", metric.Name);
+
+#pragma warning disable CS0618
Assert.Equal(42, metric.Value);
+#pragma warning restore CS0618
}
[TestMethod]
@@ -162,11 +195,17 @@ public void TrackMetricSendsSpecifiedMetricTelemetry()
var sentTelemetry = new List();
var client = this.InitializeTelemetryClient(sentTelemetry);
+#pragma warning disable CS0618
client.TrackMetric(new MetricTelemetry("TestMetric", 42));
+#pragma warning restore CS0618
var metric = (MetricTelemetry)sentTelemetry.Single();
+
Assert.Equal("TestMetric", metric.Name);
+
+#pragma warning disable CS0618
Assert.Equal(42, metric.Value);
+#pragma warning restore CS0618
}
[TestMethod]
@@ -175,11 +214,18 @@ public void TrackMetricSendsMetricTelemetryWithGivenNameValueAndProperties()
var sentTelemetry = new List();
var client = this.InitializeTelemetryClient(sentTelemetry);
+#pragma warning disable CS0618
client.TrackMetric("TestMetric", 4.2, new Dictionary { { "blah", "yoyo" } });
+#pragma warning restore CS0618
var metric = (MetricTelemetry)sentTelemetry.Single();
+
Assert.Equal("TestMetric", metric.Name);
+
+#pragma warning disable CS0618
Assert.Equal(4.2, metric.Value);
+#pragma warning restore CS0618
+
Assert.Equal("yoyo", metric.Properties["blah"]);
}
@@ -189,11 +235,16 @@ public void TrackMetricIgnoresNullPropertiesArgumentToAvoidCrashingUserApp()
var sentTelemetry = new List();
var client = this.InitializeTelemetryClient(sentTelemetry);
+#pragma warning disable CS0618
client.TrackMetric("TestMetric", 4.2, null);
+#pragma warning restore CS0618
var metric = (MetricTelemetry)sentTelemetry.Single();
+
Assert.Equal("TestMetric", metric.Name);
+#pragma warning disable CS0618
Assert.Equal(4.2, metric.Value);
+#pragma warning restore CS0618
Assert.Empty(metric.Properties);
}
diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/TransmissionSenderTest.cs b/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/TransmissionSenderTest.cs
index c592d25899..dc66dc5075 100644
--- a/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/TransmissionSenderTest.cs
+++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/Implementation/TransmissionSenderTest.cs
@@ -257,7 +257,7 @@ public void IsRaisedWhenTransmissionIsThrottledLocallyWithItems()
var telemetryItems = new List();
for (var i=0; i();
for (var i = 0; i < sender.ThrottleLimit + 10; i++)
{
- telemetryItems.Add(new DataContracts.MetricTelemetry());
+ telemetryItems.Add(new DataContracts.EventTelemetry());
}
var wrapper = new HttpWebResponseWrapper();
diff --git a/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs b/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs
index ec57e279ee..af1648df44 100644
--- a/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs
+++ b/Test/ServerTelemetryChannel.Test/Shared.Tests/SamplingTelemetryProcessorTest.cs
@@ -95,17 +95,17 @@ public void ExceptionTelemetryIsSubjectToSampling()
{
TelemetryTypeSupportsSampling(telemetryProcessors => telemetryProcessors.Process(new ExceptionTelemetry(new Exception("exception"))));
}
-
+
[TestMethod]
public void MetricTelemetryIsNotSubjectToSampling()
{
TelemetryTypeDoesNotSupportSampling(telemetryProcessors =>
{
- telemetryProcessors.Process(new MetricTelemetry("metric", 1.0));
+ telemetryProcessors.Process(new MetricTelemetry() { Count = 1, Sum = 1.0 } );
return 1;
});
}
-
+
[TestMethod]
public void PageViewTelemetryIsSubjectToSampling()
{
diff --git a/src/Core/Managed/Net45/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs b/src/Core/Managed/Net45/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs
index 6f1371fe7a..d8d8e3cbee 100644
--- a/src/Core/Managed/Net45/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs
+++ b/src/Core/Managed/Net45/Extensibility/Implementation/RichPayloadEventSource.TelemetryHandler.cs
@@ -117,6 +117,7 @@ private Action CreateHandlerForRequestTelemetry(EventSource eventSou
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as RequestTelemetry;
var data = telemetryItem.Data;
var extendedData = new
@@ -171,6 +172,7 @@ private Action CreateHandlerForTraceTelemetry(EventSource eventSourc
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as TraceTelemetry;
var data = telemetryItem.Data;
var extendedData = new
@@ -219,6 +221,7 @@ private Action CreateHandlerForEventTelemetry(EventSource eventSourc
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as EventTelemetry;
var data = telemetryItem.Data;
var extendedData = new
@@ -274,6 +277,7 @@ private Action CreateHandlerForDependencyTelemetry(EventSource event
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as DependencyTelemetry;
var data = telemetryItem.InternalData;
var extendedData = new
@@ -342,6 +346,7 @@ private Action CreateHandlerForMetricTelemetry(EventSource eventSour
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as MetricTelemetry;
var data = telemetryItem.Data;
var extendedData = new
@@ -426,6 +431,7 @@ private Action CreateHandlerForExceptionTelemetry(EventSource eventS
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as ExceptionTelemetry;
var data = telemetryItem.Data;
var extendedData = new
@@ -505,6 +511,7 @@ private Action CreateHandlerForPerformanceCounterTelemetry(EventSour
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
#pragma warning disable 618
var telemetryItem = (item as PerformanceCounterTelemetry).Data;
#pragma warning restore 618
@@ -565,6 +572,7 @@ private Action CreateHandlerForPageViewTelemetry(EventSource eventSo
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
var telemetryItem = item as PageViewTelemetry;
var data = telemetryItem.Data;
var extendedData = new
@@ -615,6 +623,7 @@ private Action CreateHandlerForSessionStateTelemetry(EventSource eve
{
if (this.EventSourceInternal.IsEnabled(EventLevel.Verbose, keywords))
{
+ item.Sanitize();
#pragma warning disable 618
var telemetryItem = (item as SessionStateTelemetry).Data;
#pragma warning restore 618
diff --git a/src/Core/Managed/Net46/Extensibility/Implementation/RichPayloadEventSource.cs b/src/Core/Managed/Net46/Extensibility/Implementation/RichPayloadEventSource.cs
index e27b7acb49..d56be7e04a 100644
--- a/src/Core/Managed/Net46/Extensibility/Implementation/RichPayloadEventSource.cs
+++ b/src/Core/Managed/Net46/Extensibility/Implementation/RichPayloadEventSource.cs
@@ -53,6 +53,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as RequestTelemetry;
this.WriteEvent(
RequestTelemetry.TelemetryName,
@@ -68,6 +69,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as TraceTelemetry;
this.WriteEvent(
TraceTelemetry.TelemetryName,
@@ -83,6 +85,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as EventTelemetry;
this.WriteEvent(
EventTelemetry.TelemetryName,
@@ -98,6 +101,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as DependencyTelemetry;
this.WriteEvent(
DependencyTelemetry.TelemetryName,
@@ -113,6 +117,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as MetricTelemetry;
this.WriteEvent(
MetricTelemetry.TelemetryName,
@@ -128,6 +133,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as ExceptionTelemetry;
this.WriteEvent(
ExceptionTelemetry.TelemetryName,
@@ -144,6 +150,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = (item as PerformanceCounterTelemetry).Data;
this.WriteEvent(
MetricTelemetry.TelemetryName,
@@ -160,6 +167,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as PageViewTelemetry;
this.WriteEvent(
PageViewTelemetry.TelemetryName,
@@ -176,6 +184,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = (item as SessionStateTelemetry).Data;
this.WriteEvent(
EventTelemetry.TelemetryName,
@@ -191,6 +200,7 @@ public void Process(ITelemetry item)
return;
}
+ item.Sanitize();
var telemetryItem = item as AvailabilityTelemetry;
this.WriteEvent(
AvailabilityTelemetry.TelemetryName,
diff --git a/src/Core/Managed/Shared/DataContracts/MetricTelemetry.cs b/src/Core/Managed/Shared/DataContracts/MetricTelemetry.cs
index 805f77ee5a..b937e77c54 100644
--- a/src/Core/Managed/Shared/DataContracts/MetricTelemetry.cs
+++ b/src/Core/Managed/Shared/DataContracts/MetricTelemetry.cs
@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
+ using System.ComponentModel;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility.Implementation;
using Microsoft.ApplicationInsights.Extensibility.Implementation.External;
@@ -18,8 +19,6 @@ public sealed class MetricTelemetry : ITelemetry, ISupportProperties
internal readonly MetricData Data;
internal readonly DataPoint Metric;
- private bool isAggregation = false;
-
///
/// Initializes a new instance of the class with empty
/// properties.
@@ -30,6 +29,8 @@ public MetricTelemetry()
this.Metric = new DataPoint();
this.Context = new TelemetryContext(this.Data.properties);
+ this.Metric.kind = DataPointType.Aggregation;
+
// We always have a single 'metric'.
this.Data.metrics.Add(this.Metric);
}
@@ -39,12 +40,42 @@ public MetricTelemetry()
/// specified and .
///
/// The is null or empty string.
+ [Obsolete("This constructor is obsolete. Use different constructor of this class to represent aggregated metric data or use EventTelemetry type to represent individual events.")]
public MetricTelemetry(string metricName, double metricValue) : this()
{
this.Name = metricName;
this.Value = metricValue;
}
+ ///
+ /// Initializes a new instance of the class with properties provided.
+ ///
+ ///
+ /// Metric statistics provided are assumed to be calculated over a period of time equaling 1 minute.
+ ///
+ /// Metric name.
+ /// Count of values taken during aggregation interval.
+ /// Sum of values taken during aggregation interval.
+ /// Minimum value taken during aggregation interval.
+ /// Maximum of values taken during aggregation interval.
+ /// Standard deviation of values taken during aggregation interval.
+ public MetricTelemetry(
+ string name,
+ int count,
+ double sum,
+ double min,
+ double max,
+ double standardDeviation)
+ : this()
+ {
+ this.Name = name;
+ this.Count = count;
+ this.Sum = sum;
+ this.Min = min;
+ this.Max = max;
+ this.StandardDeviation = standardDeviation;
+ }
+
///
/// Gets or sets date and time when event was recorded.
///
@@ -68,31 +99,33 @@ public string Name
get { return this.Metric.name; }
set { this.Metric.name = value; }
}
-
+
///
/// Gets or sets the value of this metric.
///
+ [Obsolete("This property is obsolete. Use Sum property instead.")]
public double Value
{
get { return this.Metric.value; }
set { this.Metric.value = value; }
}
+ ///
+ /// Gets or sets sum of the values of the metric samples.
+ ///
+ public double Sum
+ {
+ get { return this.Metric.value; }
+ set { this.Metric.value = value; }
+ }
+
///
/// Gets or sets the number of samples for this metric.
///
public int? Count
{
- get
- {
- return this.Metric.count;
- }
-
- set
- {
- this.Metric.count = value;
- this.UpdateKind();
- }
+ get { return this.Metric.count.HasValue ? this.Metric.count : 1; }
+ set { this.Metric.count = value; }
}
///
@@ -100,16 +133,8 @@ public double Value
///
public double? Min
{
- get
- {
- return this.Metric.min;
- }
-
- set
- {
- this.Metric.min = value;
- this.UpdateKind();
- }
+ get { return this.Metric.min; }
+ set { this.Metric.min = value; }
}
///
@@ -117,16 +142,8 @@ public double Value
///
public double? Max
{
- get
- {
- return this.Metric.max;
- }
-
- set
- {
- this.Metric.max = value;
- this.UpdateKind();
- }
+ get { return this.Metric.max; }
+ set { this.Metric.max = value; }
}
///
@@ -134,16 +151,8 @@ public double Value
///
public double? StandardDeviation
{
- get
- {
- return this.Metric.stdDev;
- }
-
- set
- {
- this.Metric.stdDev = value;
- this.UpdateKind();
- }
+ get { return this.Metric.stdDev; }
+ set { this.Metric.stdDev = value; }
}
///
@@ -162,7 +171,12 @@ void ITelemetry.Sanitize()
this.Name = this.Name.SanitizeName();
this.Name = Utils.PopulateRequiredStringValue(this.Name, "name", typeof(MetricTelemetry).FullName);
this.Properties.SanitizeProperties();
- this.Value = Utils.SanitizeNanAndInfinity(this.Value);
+ this.Sum = Utils.SanitizeNanAndInfinity(this.Sum);
+
+ // note: we set count to 1 if it isn't a postitive integer
+ // thinking that if it is zero (negative case is clearly broken)
+ // that most likely means somebody created instance but forgot to set count
+ this.Count = (!this.Count.HasValue) || (this.Count <= 0) ? 1 : this.Count;
if (this.Min.HasValue)
{
@@ -181,17 +195,5 @@ void ITelemetry.Sanitize()
this.Context.SanitizeTelemetryContext();
}
-
- private void UpdateKind()
- {
- bool isAggregation = this.Metric.count != null || this.Metric.min != null || this.Metric.max != null || this.Metric.stdDev != null;
-
- if (this.isAggregation != isAggregation)
- {
- this.Metric.kind = isAggregation ? DataPointType.Aggregation : DataPointType.Measurement;
- }
-
- this.isAggregation = isAggregation;
- }
}
}
diff --git a/src/Core/Managed/Shared/Extensibility/IMetricProcessor.cs b/src/Core/Managed/Shared/Extensibility/IMetricProcessor.cs
new file mode 100644
index 0000000000..39205e027c
--- /dev/null
+++ b/src/Core/Managed/Shared/Extensibility/IMetricProcessor.cs
@@ -0,0 +1,18 @@
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Generic;
+
+ ///
+ /// Provides functionality to process metric values prior to aggregation.
+ ///
+ public interface IMetricProcessor
+ {
+ ///
+ /// Process metric value.
+ ///
+ /// Metric definition.
+ /// Metric value.
+ void Track(Metric metric, double value);
+ }
+}
diff --git a/src/Core/Managed/Shared/Extensibility/Implementation/SimpleMetricStatisticsAggregator.cs b/src/Core/Managed/Shared/Extensibility/Implementation/SimpleMetricStatisticsAggregator.cs
new file mode 100644
index 0000000000..6d6439292e
--- /dev/null
+++ b/src/Core/Managed/Shared/Extensibility/Implementation/SimpleMetricStatisticsAggregator.cs
@@ -0,0 +1,116 @@
+namespace Microsoft.ApplicationInsights.Extensibility.Implementation
+{
+ using System;
+ using System.Threading;
+
+ ///
+ /// Represents mechanism to calculate basic statistical parameters of a series of numeric values.
+ ///
+ internal class SimpleMetricStatisticsAggregator
+ {
+ ///
+ /// Lock to make Track() method thread-safe.
+ ///
+ private SpinLock trackLock = new SpinLock();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ internal SimpleMetricStatisticsAggregator()
+ {
+ }
+
+ ///
+ /// Gets sample count.
+ ///
+ internal int Count { get; private set; }
+
+ ///
+ /// Gets sum of the samples.
+ ///
+ internal double Sum { get; private set; }
+
+ ///
+ /// Gets sum of squares of the samples.
+ ///
+ internal double SumOfSquares { get; private set; }
+
+ ///
+ /// Gets minimum sample value.
+ ///
+ internal double Min { get; private set; }
+
+ ///
+ /// Gets maximum sample value.
+ ///
+ internal double Max { get; private set; }
+
+ ///
+ /// Gets arithmetic average value in the population.
+ ///
+ internal double Average
+ {
+ get
+ {
+ return this.Count == 0 ? 0 : this.Sum / this.Count;
+ }
+ }
+
+ ///
+ /// Gets variance of the values in the population.
+ ///
+ internal double Variance
+ {
+ get
+ {
+ return this.Count == 0 ? 0 : (this.SumOfSquares / this.Count) - (this.Average * this.Average);
+ }
+ }
+
+ ///
+ /// Gets standard deviation of the values in the population.
+ ///
+ internal double StandardDeviation
+ {
+ get
+ {
+ return Math.Sqrt(this.Variance);
+ }
+ }
+
+ ///
+ /// Adds a value to the time series.
+ ///
+ /// Metric value.
+ public void Track(double value)
+ {
+ bool lockAcquired = false;
+
+ try
+ {
+ this.trackLock.Enter(ref lockAcquired);
+
+ if ((this.Count == 0) || (value < this.Min))
+ {
+ this.Min = value;
+ }
+
+ if ((this.Count == 0) || (value > this.Max))
+ {
+ this.Max = value;
+ }
+
+ this.Count++;
+ this.Sum += value;
+ this.SumOfSquares += value * value;
+ }
+ finally
+ {
+ if (lockAcquired)
+ {
+ this.trackLock.Exit();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Core/Managed/Shared/Extensibility/Implementation/Tracing/CoreEventSource.cs b/src/Core/Managed/Shared/Extensibility/Implementation/Tracing/CoreEventSource.cs
index 47fdaa0484..4a9c6b98a5 100644
--- a/src/Core/Managed/Shared/Extensibility/Implementation/Tracing/CoreEventSource.cs
+++ b/src/Core/Managed/Shared/Extensibility/Implementation/Tracing/CoreEventSource.cs
@@ -324,6 +324,43 @@ public void FailedToGetMachineName(string error, string appDomainName = "Incorre
this.nameProvider.Name);
}
+ [Event(
+ 26,
+ Message = "Failed to flush aggregated metrics. Exception: {0}.",
+ Level = EventLevel.Error)]
+ public void FailedToFlushMetricAggregators(string ex, string appDomainName = "Incorrect")
+ {
+ this.WriteEvent(
+ 26,
+ ex ?? string.Empty,
+ this.nameProvider.Name);
+ }
+
+ [Event(
+ 27,
+ Message = "Failed to snapshot aggregated metrics. Exception: {0}.",
+ Level = EventLevel.Error)]
+ public void FailedToSnapshotMetricAggregators(string ex, string appDomainName = "Incorrect")
+ {
+ this.WriteEvent(
+ 27,
+ ex ?? string.Empty,
+ this.nameProvider.Name);
+ }
+
+ [Event(
+ 28,
+ Message = "Failed to invoke metric processor '{0}'. If the issue persists, remove the processor. Exception: {1}.",
+ Level = EventLevel.Error)]
+ public void FailedToRunMetricProcessor(string processorName, string ex, string appDomainName = "Incorrect")
+ {
+ this.WriteEvent(
+ 28,
+ processorName ?? string.Empty,
+ ex ?? string.Empty,
+ this.nameProvider.Name);
+ }
+
///
/// Keywords for the PlatformEventSource.
///
diff --git a/src/Core/Managed/Shared/Extensibility/Metric.cs b/src/Core/Managed/Shared/Extensibility/Metric.cs
new file mode 100644
index 0000000000..5aa9a0d3b4
--- /dev/null
+++ b/src/Core/Managed/Shared/Extensibility/Metric.cs
@@ -0,0 +1,175 @@
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+
+ using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
+
+ ///
+ /// Represents aggregator for a single time series of a given metric.
+ ///
+ public class Metric : IEquatable
+ {
+ ///
+ /// Aggregator manager for the aggregator.
+ ///
+ private readonly MetricManager manager;
+
+ ///
+ /// Metric aggregator id to look for in the aggregator dictionary.
+ ///
+ private readonly string aggregatorId;
+
+ ///
+ /// Aggregator hash code.
+ ///
+ private readonly int hashCode;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Aggregator manager handling this instance.
+ /// Metric name.
+ /// Metric dimensions.
+ internal Metric(
+ MetricManager manager,
+ string name,
+ IDictionary dimensions = null)
+ {
+ if (manager == null)
+ {
+ throw new ArgumentNullException("manager");
+ }
+
+ this.manager = manager;
+ this.Name = name;
+ this.Dimensions = dimensions;
+
+ this.aggregatorId = Metric.GetAggregatorId(name, dimensions);
+ this.hashCode = this.aggregatorId.GetHashCode();
+ }
+
+ ///
+ /// Gets metric name.
+ ///
+ public string Name { get; private set; }
+
+ ///
+ /// Gets a set of metric dimensions and their values.
+ ///
+ public IDictionary Dimensions { get; private set; }
+
+ ///
+ /// Adds a value to the time series.
+ ///
+ /// Metric value.
+ public void Track(double value)
+ {
+ SimpleMetricStatisticsAggregator aggregator = this.manager.GetStatisticsAggregator(this);
+ aggregator.Track(value);
+
+ this.ForwardToProcessors(value);
+ }
+
+ ///
+ /// Returns the hash code for this object.
+ ///
+ /// A 32-bit signed integer hash code.
+ public override int GetHashCode()
+ {
+ return this.hashCode;
+ }
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// True if the specified object is equal to the current object; otherwise, false.
+ public bool Equals(Metric other)
+ {
+ if (other == null)
+ {
+ return false;
+ }
+
+ return this.aggregatorId.Equals(other.aggregatorId, StringComparison.Ordinal);
+ }
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// True if the specified object is equal to the current object; otherwise, false.
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ return this.Equals(obj as Metric);
+ }
+
+ ///
+ /// Generates id of the aggregator serving time series specified in the parameters.
+ ///
+ /// Metric name.
+ /// Optional metric dimensions.
+ /// Aggregator id that can be used to get aggregator.
+ private static string GetAggregatorId(string name, IDictionary dimensions = null)
+ {
+ StringBuilder aggregatorIdBuilder = new StringBuilder(name ?? "n/a");
+
+ if (dimensions != null)
+ {
+ var sortedDimensions = dimensions.OrderBy((pair) => { return pair.Key; });
+
+ foreach (KeyValuePair pair in sortedDimensions)
+ {
+ aggregatorIdBuilder.AppendFormat(CultureInfo.InvariantCulture, "\n{0}\t{1}", pair.Key ?? string.Empty, pair.Value ?? string.Empty);
+ }
+ }
+
+ return aggregatorIdBuilder.ToString();
+ }
+
+ ///
+ /// Forwards value to metric processors.
+ ///
+ /// Value tracked on time series.
+ private void ForwardToProcessors(double value)
+ {
+ // create a local reference to metric processor collection
+ // if collection changes after that - it will be copied not affecting local reference
+ IList metricProcessors = this.manager.MetricProcessors;
+
+ if (metricProcessors != null)
+ {
+ int processorCount = metricProcessors.Count;
+
+ for (int i = 0; i < processorCount; i++)
+ {
+ IMetricProcessor processor = metricProcessors[i];
+
+ try
+ {
+ processor.Track(this, value);
+ }
+ catch (Exception ex)
+ {
+ CoreEventSource.Log.FailedToRunMetricProcessor(processor.GetType().FullName, ex.ToString());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Core/Managed/Shared/Extensibility/MetricManager.cs b/src/Core/Managed/Shared/Extensibility/MetricManager.cs
new file mode 100644
index 0000000000..3fa46837ad
--- /dev/null
+++ b/src/Core/Managed/Shared/Extensibility/MetricManager.cs
@@ -0,0 +1,256 @@
+namespace Microsoft.ApplicationInsights.Extensibility
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ using Microsoft.ApplicationInsights.DataContracts;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation;
+ using Microsoft.ApplicationInsights.Extensibility.Implementation.Tracing;
+
+#if CORE_PCL || NET45 || NET46
+ using TaskEx = System.Threading.Tasks.Task;
+#endif
+
+ ///
+ /// Metric factory and controller.
+ ///
+ public sealed class MetricManager : IDisposable
+ {
+ ///
+ /// Name of the property added to aggregation results to indicate duration of the aggregation interval.
+ ///
+ private static string intervalDurationPropertyName = "IntervalDurationMs";
+
+ ///
+ /// Reporting frequency.
+ ///
+ private static TimeSpan aggregationPeriod = TimeSpan.FromMinutes(1);
+
+ ///
+ /// Telemetry client used to track resulting aggregated metrics.
+ ///
+ private readonly TelemetryClient telemetryClient;
+
+ ///
+ /// Metric aggregation snapshot task.
+ ///
+ private TaskTimer snapshotTimer;
+
+ ///
+ /// Last time snapshot was initiated.
+ ///
+ private DateTimeOffset lastSnapshotStartDateTime;
+
+ ///
+ /// A dictionary of all metrics instantiated via this manager.
+ ///
+ private ConcurrentDictionary metricDictionary;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MetricManager()
+ : this(new TelemetryClient())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Telemetry client to use to output aggregated metric data.
+ public MetricManager(TelemetryClient client)
+ {
+ this.telemetryClient = client ?? new TelemetryClient();
+ this.metricDictionary = new ConcurrentDictionary();
+
+ this.lastSnapshotStartDateTime = DateTimeOffset.UtcNow;
+
+ this.snapshotTimer = new TaskTimer() { Delay = GetWaitTime() };
+ this.snapshotTimer.Start(this.SnapshotAndReschedule);
+ }
+
+ ///
+ /// Gets a list of metric processors associated
+ /// with this instance of .
+ ///
+ internal IList MetricProcessors
+ {
+ get
+ {
+ TelemetryConfiguration config = this.telemetryClient.TelemetryConfiguration;
+
+ return config.MetricProcessors;
+ }
+ }
+
+ ///
+ /// Creates metric.
+ ///
+ /// Name of the metric.
+ /// Optional dimensions.
+ /// Metric instance.
+ public Metric CreateMetric(string name, IDictionary dimensions = null)
+ {
+ return new Metric(this, name, dimensions);
+ }
+
+ ///
+ /// Flushes the in-memory aggregation buffers.
+ ///
+ public void Flush()
+ {
+ try
+ {
+ this.Snapshot();
+ this.telemetryClient.Flush();
+ }
+ catch (Exception ex)
+ {
+ CoreEventSource.Log.FailedToFlushMetricAggregators(ex.ToString());
+ }
+ }
+
+ ///
+ /// Disposes the object.
+ ///
+ public void Dispose()
+ {
+ if (this.snapshotTimer != null)
+ {
+ this.snapshotTimer.Dispose();
+ this.snapshotTimer = null;
+ }
+
+ this.Flush();
+ }
+
+ internal SimpleMetricStatisticsAggregator GetStatisticsAggregator(Metric metric)
+ {
+ return this.metricDictionary.GetOrAdd(metric, (m) => { return new SimpleMetricStatisticsAggregator(); });
+ }
+
+ ///
+ /// Calculates wait time until next snapshot of the aggregators.
+ ///
+ /// Wait time.
+ private static TimeSpan GetWaitTime()
+ {
+ DateTimeOffset currentTime = DateTimeOffset.UtcNow;
+
+ double minutesFromZero = currentTime.Subtract(DateTimeOffset.MinValue).TotalMinutes;
+
+ // we want to wake up exactly at 1 second past minute
+ // to make perceived system latency look smaller
+ var nextWakeTime = DateTimeOffset.MinValue
+ .AddMinutes((long)minutesFromZero)
+ .Add(aggregationPeriod)
+ .AddSeconds(1);
+
+ TimeSpan sleepTime = nextWakeTime - DateTimeOffset.UtcNow;
+
+ // adjust wait time to a bit longer than a minute if the wake up time is within few seconds from now
+ return sleepTime < TimeSpan.FromSeconds(3) ? sleepTime.Add(aggregationPeriod) : sleepTime;
+ }
+
+ ///
+ /// Generates telemetry object based on the metric aggregator.
+ ///
+ /// Metric definition.
+ /// Metric aggregator statistics calculated for a period of time.
+ /// Metric telemetry object resulting from aggregation.
+ private static MetricTelemetry CreateAggergatedMetricTelemetry(Metric metric, SimpleMetricStatisticsAggregator statistics)
+ {
+ var telemetry = new MetricTelemetry(
+ metric.Name,
+ statistics.Count,
+ statistics.Sum,
+ statistics.Min,
+ statistics.Max,
+ statistics.StandardDeviation);
+
+ if (metric.Dimensions != null)
+ {
+ foreach (KeyValuePair property in metric.Dimensions)
+ {
+ telemetry.Properties.Add(property);
+ }
+ }
+
+ return telemetry;
+ }
+
+ ///
+ /// Takes a snapshot of aggregators collected by this instance of the manager
+ /// and schedules the next snapshot.
+ ///
+ private Task SnapshotAndReschedule()
+ {
+ return Task.Factory.StartNew(
+ () =>
+ {
+ try
+ {
+ this.Snapshot();
+ }
+ catch (Exception ex)
+ {
+ CoreEventSource.Log.FailedToSnapshotMetricAggregators(ex.ToString());
+ }
+ finally
+ {
+ this.snapshotTimer.Delay = GetWaitTime();
+ this.snapshotTimer.Start(this.SnapshotAndReschedule);
+ }
+ });
+ }
+
+ ///
+ /// Takes snapshot of all active metric aggregators and turns results into metric telemetry.
+ ///
+ private void Snapshot()
+ {
+ ConcurrentDictionary aggregatorSnapshot =
+ Interlocked.Exchange(ref this.metricDictionary, new ConcurrentDictionary());
+
+ // calculate aggregation interval duration interval
+ TimeSpan aggregationIntervalDuation = DateTimeOffset.UtcNow - this.lastSnapshotStartDateTime;
+ this.lastSnapshotStartDateTime = DateTimeOffset.UtcNow;
+
+ // prevent zero duration for interval
+ if (aggregationIntervalDuation.TotalMilliseconds < 1)
+ {
+ aggregationIntervalDuation = TimeSpan.FromMilliseconds(1);
+ }
+
+ // adjust interval duration to exactly snapshot frequency if it is close (within 1%)
+ double difference = Math.Abs(aggregationIntervalDuation.TotalMilliseconds - aggregationPeriod.TotalMilliseconds);
+
+ if (difference <= aggregationPeriod.TotalMilliseconds / 100)
+ {
+ aggregationIntervalDuation = aggregationPeriod;
+ }
+
+ if (aggregatorSnapshot.Count > 0)
+ {
+ foreach (KeyValuePair aggregatorWithStats in aggregatorSnapshot)
+ {
+ if (aggregatorWithStats.Value.Count > 0)
+ {
+ MetricTelemetry aggergatedMetricTelemetry = CreateAggergatedMetricTelemetry(aggregatorWithStats.Key, aggregatorWithStats.Value);
+
+ aggergatedMetricTelemetry.Properties.Add(intervalDurationPropertyName, ((long)aggregationIntervalDuation.TotalMilliseconds).ToString(CultureInfo.InvariantCulture));
+
+ // set the timestamp back by aggregation period
+ aggergatedMetricTelemetry.Timestamp = DateTimeOffset.Now - aggregationPeriod;
+
+ this.telemetryClient.Track(aggergatedMetricTelemetry);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Core/Managed/Shared/Extensibility/TelemetryConfiguration.cs b/src/Core/Managed/Shared/Extensibility/TelemetryConfiguration.cs
index fb9b7daaf4..48c12ba97b 100644
--- a/src/Core/Managed/Shared/Extensibility/TelemetryConfiguration.cs
+++ b/src/Core/Managed/Shared/Extensibility/TelemetryConfiguration.cs
@@ -26,6 +26,7 @@ public sealed class TelemetryConfiguration : IDisposable
private string instrumentationKey = string.Empty;
private bool disableTelemetry = false;
private TelemetryProcessorChainBuilder builder;
+ private SnapshottingList metricProcessors = new SnapshottingList();
///
/// Gets the active instance loaded from the ApplicationInsights.config file.
@@ -132,6 +133,15 @@ public IList TelemetryInitializers
get { return this.telemetryInitializers; }
}
+ ///
+ /// Gets the list of objects used for custom metric data processing
+ /// before client-side metric aggregation process.
+ ///
+ public IList MetricProcessors
+ {
+ get { return this.metricProcessors; }
+ }
+
///
/// Gets a readonly collection of TelemetryProcessors.
///
diff --git a/src/Core/Managed/Shared/Shared.projitems b/src/Core/Managed/Shared/Shared.projitems
index fcd2aeaf4a..f72e4cedca 100644
--- a/src/Core/Managed/Shared/Shared.projitems
+++ b/src/Core/Managed/Shared/Shared.projitems
@@ -38,6 +38,7 @@
+
@@ -58,6 +59,7 @@
+
@@ -116,6 +118,8 @@
+
+
diff --git a/src/Core/Managed/Shared/TelemetryClient.cs b/src/Core/Managed/Shared/TelemetryClient.cs
index 49251e426a..c9a9964e1a 100644
--- a/src/Core/Managed/Shared/TelemetryClient.cs
+++ b/src/Core/Managed/Shared/TelemetryClient.cs
@@ -189,6 +189,8 @@ public void TrackTrace(TraceTelemetry telemetry)
/// Metric name.
/// Metric value.
/// Named string values you can use to classify and filter metrics.
+ [Obsolete("This method is obsolete. Use TrackMetric(metricTelemetry) method to send pre-aggregated metric data or MetricManager class to create metrics.")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void TrackMetric(string name, double value, IDictionary properties = null)
{
var telemetry = new MetricTelemetry(name, value);
@@ -201,7 +203,7 @@ public void TrackMetric(string name, double value, IDictionary p
}
///
- /// Send a for aggregation in Metric Explorer.
+ /// Send a for representing aggregated metric data.
/// Create a separate instance for each call to .
///
public void TrackMetric(MetricTelemetry telemetry)
@@ -359,8 +361,7 @@ public void Track(ITelemetry telemetry)
this.configuration.TelemetryProcessorChain.Process(telemetry);
#if !CORE_PCL
- // logs rich payload ETW event for any partners to process it
- telemetry.Sanitize();
+ // logs rich payload ETW event for any partners to process it
RichPayloadEventSource.Log.Process(telemetry);
#endif
}