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);
            }
        }
Esempio n. 6
0
        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);
            }
        }