public async Task RequestMetricIsCaptured() { var metricItems = new List <MetricItem>(); var metricExporter = new TestExporter <MetricItem>(ProcessExport); void ProcessExport(Batch <MetricItem> batch) { foreach (var metricItem in batch) { metricItems.Add(metricItem); } } var processor = new PullMetricProcessor(metricExporter, true); this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddAspNetCoreInstrumentation() .AddMetricProcessor(processor) .Build(); using (var client = this.factory.CreateClient()) { var response = await client.GetAsync("/api/values"); response.EnsureSuccessStatusCode(); } // We need to let End callback execute as it is executed AFTER response was returned. // In unit tests environment there may be a lot of parallel unit tests executed, so // giving some breezing room for the End callback to complete await Task.Delay(TimeSpan.FromSeconds(1)); // Invokes the TestExporter which will invoke ProcessExport processor.PullRequest(); this.meterProvider.Dispose(); var requestMetrics = metricItems .SelectMany(item => item.Metrics.Where(metric => metric.Name == "http.server.duration")) .ToArray(); Assert.True(requestMetrics.Length == 1); var metric = requestMetrics[0] as IHistogramMetric; Assert.NotNull(metric); Assert.Equal(1L, metric.PopulationCount); Assert.True(metric.PopulationSum > 0); var bucket = metric.Buckets .Where(b => metric.PopulationSum > b.LowBoundary && metric.PopulationSum <= b.HighBoundary) .FirstOrDefault(); Assert.NotEqual(default, bucket);
public void ToOtlpResourceMetricsTest(bool includeServiceNameInResource) { using var exporter = new OtlpMetricsExporter( new OtlpExporterOptions(), new NoopMetricsServiceClient()); var resourceBuilder = ResourceBuilder.CreateEmpty(); if (includeServiceNameInResource) { resourceBuilder.AddAttributes( new List <KeyValuePair <string, object> > { new KeyValuePair <string, object>(ResourceSemanticConventions.AttributeServiceName, "service-name"), new KeyValuePair <string, object>(ResourceSemanticConventions.AttributeServiceNamespace, "ns1"), }); } var tags = new KeyValuePair <string, object>[] { new KeyValuePair <string, object>("key1", "value1"), new KeyValuePair <string, object>("key2", "value2"), }; var processor = new PullMetricProcessor(new TestExporter <MetricItem>(RunTest), true); using var provider = Sdk.CreateMeterProviderBuilder() .SetResourceBuilder(resourceBuilder) .AddSource("TestMeter") .AddMetricProcessor(processor) .Build(); exporter.ParentProvider = provider; using var meter = new Meter("TestMeter", "0.0.1"); var counter = meter.CreateCounter <int>("counter"); counter.Add(100, tags); var testCompleted = false; // Invokes the TestExporter which will invoke RunTest processor.PullRequest(); Assert.True(testCompleted); void RunTest(Batch <MetricItem> metricItem) { var request = new OtlpCollector.ExportMetricsServiceRequest(); request.AddBatch(exporter.ProcessResource, metricItem); Assert.Single(request.ResourceMetrics); var resourceMetric = request.ResourceMetrics.First(); var oltpResource = resourceMetric.Resource; if (includeServiceNameInResource) { Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service-name"); Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns1"); } else { Assert.Contains(oltpResource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:")); } Assert.Single(resourceMetric.InstrumentationLibraryMetrics); var instrumentationLibraryMetrics = resourceMetric.InstrumentationLibraryMetrics.First(); Assert.Equal(string.Empty, instrumentationLibraryMetrics.SchemaUrl); Assert.Equal("TestMeter", instrumentationLibraryMetrics.InstrumentationLibrary.Name); Assert.Equal("0.0.1", instrumentationLibraryMetrics.InstrumentationLibrary.Version); Assert.Single(instrumentationLibraryMetrics.Metrics); foreach (var metric in instrumentationLibraryMetrics.Metrics) { Assert.Equal(string.Empty, metric.Description); Assert.Equal(string.Empty, metric.Unit); Assert.Equal("counter", metric.Name); Assert.Equal(OtlpMetrics.Metric.DataOneofCase.Sum, metric.DataCase); Assert.True(metric.Sum.IsMonotonic); Assert.Equal(OtlpMetrics.AggregationTemporality.Delta, metric.Sum.AggregationTemporality); Assert.Single(metric.Sum.DataPoints); var dataPoint = metric.Sum.DataPoints.First(); Assert.True(dataPoint.StartTimeUnixNano > 0); Assert.True(dataPoint.TimeUnixNano > 0); Assert.Equal(OtlpMetrics.NumberDataPoint.ValueOneofCase.AsInt, dataPoint.ValueCase); Assert.Equal(100, dataPoint.AsInt); #pragma warning disable CS0612 // Type or member is obsolete Assert.Empty(dataPoint.Labels); #pragma warning restore CS0612 // Type or member is obsolete OtlpTestHelpers.AssertOtlpAttributes(tags.ToList(), dataPoint.Attributes); Assert.Empty(dataPoint.Exemplars); } testCompleted = true; } }
public void CounterAggregationTest(bool exportDelta) { var metricItems = new List <MetricItem>(); var metricExporter = new TestExporter <MetricItem>(ProcessExport); void ProcessExport(Batch <MetricItem> batch) { foreach (var metricItem in batch) { metricItems.Add(metricItem); } } var pullProcessor = new PullMetricProcessor(metricExporter, exportDelta); var meter = new Meter("TestMeter"); var counterLong = meter.CreateCounter <long>("mycounter"); var meterProvider = Sdk.CreateMeterProviderBuilder() .AddSource("TestMeter") .AddMetricProcessor(pullProcessor) .Build(); counterLong.Add(10); counterLong.Add(10); pullProcessor.PullRequest(); long sumReceived = GetSum(metricItems); Assert.Equal(20, sumReceived); metricItems.Clear(); counterLong.Add(10); counterLong.Add(10); pullProcessor.PullRequest(); sumReceived = GetSum(metricItems); if (exportDelta) { Assert.Equal(20, sumReceived); } else { Assert.Equal(40, sumReceived); } metricItems.Clear(); pullProcessor.PullRequest(); sumReceived = GetSum(metricItems); if (exportDelta) { Assert.Equal(0, sumReceived); } else { Assert.Equal(40, sumReceived); } metricItems.Clear(); counterLong.Add(40); counterLong.Add(20); pullProcessor.PullRequest(); sumReceived = GetSum(metricItems); if (exportDelta) { Assert.Equal(60, sumReceived); } else { Assert.Equal(100, sumReceived); } }
public void SimpleTest() { var metricItems = new List <MetricItem>(); var metricExporter = new TestExporter <MetricItem>(ProcessExport); void ProcessExport(Batch <MetricItem> batch) { foreach (var metricItem in batch) { metricItems.Add(metricItem); } } var pullProcessor = new PullMetricProcessor(metricExporter, true); var meter = new Meter("TestMeter"); var counterLong = meter.CreateCounter <long>("mycounter"); var meterProvider = Sdk.CreateMeterProviderBuilder() .AddSource("TestMeter") .AddMetricProcessor(pullProcessor) .Build(); // setup args to threads. var mreToBlockUpdateThreads = new ManualResetEvent(false); var mreToEnsureAllThreadsStarted = new ManualResetEvent(false); var argToThread = new UpdateThreadArguments(); argToThread.Counter = counterLong; argToThread.ThreadsStartedCount = 0; argToThread.MreToBlockUpdateThread = mreToBlockUpdateThreads; argToThread.MreToEnsureAllThreadsStart = mreToEnsureAllThreadsStarted; Thread[] t = new Thread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) { t[i] = new Thread(CounterUpdateThread); t[i].Start(argToThread); } // Block until all threads started. mreToEnsureAllThreadsStarted.WaitOne(); Stopwatch sw = new Stopwatch(); sw.Start(); // unblock all the threads. // (i.e let them start counter.Add) mreToBlockUpdateThreads.Set(); for (int i = 0; i < numberOfThreads; i++) { // wait for all threads to complete t[i].Join(); } var timeTakenInMilliseconds = sw.ElapsedMilliseconds; this.output.WriteLine($"Took {timeTakenInMilliseconds} msecs. Total threads: {numberOfThreads}, each thread doing {numberOfMetricUpdateByEachThread} recordings."); meterProvider.Dispose(); pullProcessor.PullRequest(); long sumReceived = 0; foreach (var metricItem in metricItems) { var metrics = metricItem.Metrics; foreach (var metric in metrics) { sumReceived += (metric as ISumMetricLong).LongSum; } } var expectedSum = deltaValueUpdatedByEachCall * numberOfMetricUpdateByEachThread * numberOfThreads; Assert.Equal(expectedSum, sumReceived); }
public void ObservableCounterAggregationTest(bool exportDelta) { var meterName = "TestMeter" + exportDelta; var metricItems = new List <MetricItem>(); var metricExporter = new TestExporter <MetricItem>(ProcessExport); void ProcessExport(Batch <MetricItem> batch) { foreach (var metricItem in batch) { metricItems.Add(metricItem); } } var pullProcessor = new PullMetricProcessor(metricExporter, exportDelta); var meter = new Meter(meterName); int i = 1; var counterLong = meter.CreateObservableCounter <long>( "observable-counter", () => { return(new List <Measurement <long> >() { new Measurement <long>(i++ *10), }); }); var meterProvider = Sdk.CreateMeterProviderBuilder() .AddSource(meterName) .AddMetricProcessor(pullProcessor) .Build(); pullProcessor.PullRequest(); long sumReceived = GetSum(metricItems); Assert.Equal(10, sumReceived); metricItems.Clear(); pullProcessor.PullRequest(); sumReceived = GetSum(metricItems); if (exportDelta) { Assert.Equal(10, sumReceived); } else { Assert.Equal(20, sumReceived); } metricItems.Clear(); pullProcessor.PullRequest(); sumReceived = GetSum(metricItems); if (exportDelta) { Assert.Equal(10, sumReceived); } else { Assert.Equal(30, sumReceived); } }
public async Task HttpOutCallsAreCollectedSuccessfullyAsync(HttpTestData.HttpOutTestCase tc) { var serverLifeTime = TestHttpServer.RunServer( (ctx) => { ctx.Response.StatusCode = tc.ResponseCode == 0 ? 200 : tc.ResponseCode; ctx.Response.OutputStream.Close(); }, out var host, out var port); var processor = new Mock <BaseProcessor <Activity> >(); tc.Url = HttpTestData.NormalizeValues(tc.Url, host, port); var metricItems = new List <MetricItem>(); var metricExporter = new TestExporter <MetricItem>(ProcessExport); void ProcessExport(Batch <MetricItem> batch) { foreach (var metricItem in batch) { metricItems.Add(metricItem); } } var metricProcessor = new PullMetricProcessor(metricExporter, true); var meterProvider = Sdk.CreateMeterProviderBuilder() .AddHttpClientInstrumentation() .AddMetricProcessor(metricProcessor) .Build(); using (serverLifeTime) using (Sdk.CreateTracerProviderBuilder() .AddHttpClientInstrumentation((opt) => { opt.SetHttpFlavor = tc.SetHttpFlavor; opt.Enrich = ActivityEnrichment; opt.RecordException = tc.RecordException.HasValue ? tc.RecordException.Value : false; }) .AddProcessor(processor.Object) .Build()) { try { using var c = new HttpClient(); var request = new HttpRequestMessage { RequestUri = new Uri(tc.Url), Method = new HttpMethod(tc.Method), Version = new Version(2, 0), }; if (tc.Headers != null) { foreach (var header in tc.Headers) { request.Headers.Add(header.Key, header.Value); } } await c.SendAsync(request); } catch (Exception) { // test case can intentionally send request that will result in exception } } // Invokes the TestExporter which will invoke ProcessExport metricProcessor.PullRequest(); meterProvider.Dispose(); var requestMetrics = metricItems .SelectMany(item => item.Metrics.Where(metric => metric.Name == "http.client.duration")) .ToArray(); Assert.Equal(5, processor.Invocations.Count); // SetParentProvider/OnStart/OnEnd/OnShutdown/Dispose called. var activity = (Activity)processor.Invocations[2].Arguments[0]; Assert.Equal(ActivityKind.Client, activity.Kind); Assert.Equal(tc.SpanName, activity.DisplayName); // Assert.Equal(tc.SpanStatus, d[span.Status.CanonicalCode]); Assert.Equal( tc.SpanStatus, activity.GetTagValue(SpanAttributeConstants.StatusCodeKey) as string); if (tc.SpanStatusHasDescription.HasValue) { var desc = activity.GetTagValue(SpanAttributeConstants.StatusDescriptionKey) as string; Assert.Equal(tc.SpanStatusHasDescription.Value, !string.IsNullOrEmpty(desc)); } var normalizedAttributes = activity.TagObjects.Where(kv => !kv.Key.StartsWith("otel.")).ToImmutableSortedDictionary(x => x.Key, x => x.Value.ToString()); var normalizedAttributesTestCase = tc.SpanAttributes.ToDictionary(x => x.Key, x => HttpTestData.NormalizeValues(x.Value, host, port)); Assert.Equal(normalizedAttributesTestCase.Count, normalizedAttributes.Count); foreach (var kv in normalizedAttributesTestCase) { Assert.Contains(activity.TagObjects, i => i.Key == kv.Key && i.Value.ToString().Equals(kv.Value, StringComparison.InvariantCultureIgnoreCase)); } if (tc.RecordException.HasValue && tc.RecordException.Value) { Assert.Single(activity.Events.Where(evt => evt.Name.Equals("exception"))); } if (tc.ResponseExpected) { Assert.Single(requestMetrics); var metric = requestMetrics[0] as IHistogramMetric; Assert.NotNull(metric); Assert.Equal(1L, metric.PopulationCount); Assert.Equal(activity.Duration.TotalMilliseconds, metric.PopulationSum); var method = new KeyValuePair <string, object>(SemanticConventions.AttributeHttpMethod, tc.Method); var scheme = new KeyValuePair <string, object>(SemanticConventions.AttributeHttpScheme, "http"); var statusCode = new KeyValuePair <string, object>(SemanticConventions.AttributeHttpStatusCode, tc.ResponseCode == 0 ? 200 : tc.ResponseCode); var flavor = new KeyValuePair <string, object>(SemanticConventions.AttributeHttpFlavor, "2.0"); Assert.Contains(method, metric.Attributes); Assert.Contains(scheme, metric.Attributes); Assert.Contains(statusCode, metric.Attributes); Assert.Contains(flavor, metric.Attributes); Assert.Equal(4, metric.Attributes.Length); } else { Assert.Empty(requestMetrics); } }