/// <summary>
        /// Asserts on 1 async transaction and 1 error
        /// </summary>
        private async Task <MockPayloadSender> AssertWith1TransactionAnd1ErrorAsync(Func <ApmAgent, Task> func)
        {
            var payloadSender = new MockPayloadSender();
            var agent         = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            await func(agent);

            payloadSender.Transactions.Should().NotBeEmpty();
            payloadSender.Transactions.Should().NotBeEmpty();

            payloadSender.FirstTransaction.Name.Should().Be(TransactionName);
            payloadSender.FirstTransaction.Type.Should().Be(TransactionType);

            var duration = payloadSender.FirstTransaction.Duration;

            duration.Should().BeGreaterOrEqualToMinimumSleepLength();

            payloadSender.Errors.Should().NotBeEmpty();
            payloadSender.Errors.Should().NotBeEmpty();

            payloadSender.FirstError.Exception.Type.Should().Be(typeof(InvalidOperationException).FullName);
            payloadSender.FirstError.Exception.Message.Should().Be(ExceptionMessage);
            return(payloadSender);
        }
Example #2
0
        public void TransactionWithSpan()
        {
            var transactionName = "TestTransaction";
            var transactionType = "UnitTest";
            var spanName        = "TestSpan";
            var payloadSender   = new MockPayloadSender();
            var agent           = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            var transaction = agent.Tracer.StartTransaction(transactionName, transactionType);

            var span = transaction.StartSpan(spanName, ApiConstants.TypeExternal);

            Thread.Sleep(5);             //Make sure we have duration > 0

            span.End();
            transaction.End();
            Assert.NotEmpty(payloadSender.Payloads);
            Assert.NotEmpty(payloadSender.Payloads[0].Transactions[0].Spans);

            Assert.Equal(spanName, payloadSender.Payloads[0].Transactions[0].Spans[0].Name);
            Assert.True(payloadSender.Payloads[0].Transactions[0].Spans[0].Duration >= 5);
            Assert.True(payloadSender.Payloads[0].Transactions[0].Spans[0].Id >= 5);
            Assert.NotNull(payloadSender.Payloads[0].Service);
        }
Example #3
0
        public void DefaultStackTraceLimitAndSpanFramesMinDuration_ShortSpan()
        {
            var payloadSender = new MockPayloadSender();

            using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)))
            {
                agent.Tracer.CaptureTransaction("TestTransaction", "Test", t
                                                =>
                {
                    t.CaptureSpan("span", "span", () => { });
                });
            }

            payloadSender.FirstSpan.Should().NotBeNull();

            if (payloadSender.FirstSpan.Duration < ConfigConsts.DefaultValues.SpanFramesMinDurationInMilliseconds)
            {
                payloadSender.FirstSpan.StackTrace.Should().BeNullOrEmpty();
            }
            else
            {
                payloadSender.FirstSpan.StackTrace.Should().NotBeNullOrEmpty();
            }
        }
        public async Task CancelledAsyncTask()
        {
            var payloadSender = new MockPayloadSender();
            var agent         = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            var cancellationTokenSource = new CancellationTokenSource();
            var token = cancellationTokenSource.Token;

            cancellationTokenSource.Cancel();

            Func <Task> act = async() =>
            {
                await agent.Tracer.CaptureTransaction(TransactionName, TransactionType,
                                                      async() =>
                {
                    // ReSharper disable once MethodSupportsCancellation, we want to delay before we throw the exception
                    await WaitHelpers.DelayMinimum();
                    token.ThrowIfCancellationRequested();
                });
            };
            await act.Should().ThrowAsync <OperationCanceledException>();

            payloadSender.Transactions.Should().NotBeEmpty();

            payloadSender.FirstTransaction.Name.Should().Be(TransactionName);
            payloadSender.FirstTransaction.Type.Should().Be(TransactionType);

            var duration = payloadSender.FirstTransaction.Duration;

            duration.Should().BeGreaterOrEqualToMinimumSleepLength();

            payloadSender.Errors.Should().NotBeEmpty();

            payloadSender.FirstError.Culprit.Should().Be("A task was canceled");
            payloadSender.FirstError.Exception.Message.Should().Be("Task canceled");
        }
    /// <summary>
    /// Asserts on 1 transaction with 1 async span and 1 error
    /// </summary>
    private async Task <MockPayloadSender> AssertWith1TransactionAnd1ErrorAnd1SpanAsync(Func <ITransaction, Task> func)
    {
        var payloadSender = new MockPayloadSender();

        using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

        await agent.Tracer.CaptureTransaction(TransactionName, TransactionType, async t =>
        {
            await WaitHelpers.DelayMinimum();
            await func(t);
        });

        payloadSender.WaitForTransactions();
        payloadSender.Transactions.Should().NotBeEmpty();

        payloadSender.FirstTransaction.Name.Should().Be(TransactionName);
        payloadSender.FirstTransaction.Type.Should().Be(TransactionType);

        var duration = payloadSender.FirstTransaction.Duration;

        duration.Should().BeGreaterOrEqualToMinimumSleepLength(3);

        payloadSender.WaitForSpans();
        payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty();

        payloadSender.SpansOnFirstTransaction[0].Name.Should().Be(SpanName);
        payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(SpanType);

        payloadSender.WaitForErrors();
        payloadSender.Errors.Should().NotBeEmpty();

        payloadSender.FirstError.Exception.Type.Should().Be(typeof(InvalidOperationException).FullName);
        payloadSender.FirstError.Exception.Message.Should().Be(ExceptionMessage);

        return(payloadSender);
    }
Example #6
0
        public void TransactionWithSpanWithSubTypeAndAction()
        {
            const string transactionName = TestTransaction;
            const string transactionType = UnitTest;
            const string spanName        = "TestSpan";
            var          payloadSender   = new MockPayloadSender();

            using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)))
            {
                var transaction = agent.Tracer.StartTransaction(transactionName, transactionType);
                var span        = transaction.StartSpan(spanName, ApiConstants.TypeDb, ApiConstants.SubtypeMssql, ApiConstants.ActionQuery);
                span.End();
                transaction.End();

                payloadSender.Transactions.Should().NotBeEmpty();
                payloadSender.SpansOnFirstTransaction.Should().NotBeEmpty();

                payloadSender.SpansOnFirstTransaction[0].Type.Should().Be(ApiConstants.TypeDb);
                payloadSender.SpansOnFirstTransaction[0].Subtype.Should().Be(ApiConstants.SubtypeMssql);
                payloadSender.SpansOnFirstTransaction[0].Action.Should().Be(ApiConstants.ActionQuery);

                agent.Service.Should().NotBeNull();
            }
        }
Example #7
0
        public void DisableMetrics_DisableCpuMetrics()
        {
            var mockPayloadSender = new MockPayloadSender();

            var noopLogger = new NoopLogger();

            using var metricsProvider =
                      new MetricsCollector(noopLogger, mockPayloadSender, new ConfigStore(new MockConfigSnapshot(disableMetrics: "*cpu*"), noopLogger));
            metricsProvider.CollectAllMetrics();

            mockPayloadSender.Metrics.Should().NotBeEmpty();

            var firstMetrics = mockPayloadSender.Metrics.First();

            firstMetrics.Should().NotBeNull();

            firstMetrics.Samples.Should().NotContain(n => n.KeyValue.Key.Contains("cpu"));

            //These are collected on all platforms, with the given config they always should be there
            firstMetrics.Samples.Should()
            .Contain(n => n.KeyValue.Key.Equals("system.process.memory.size", StringComparison.InvariantCultureIgnoreCase));
            firstMetrics.Samples.Should()
            .Contain(n => n.KeyValue.Key.Equals("system.process.memory.rss.bytes", StringComparison.InvariantCultureIgnoreCase));
        }
    public void FillSpanContext()
    {
        var payloadSender = new MockPayloadSender();

        using var agent =
                  new ApmAgent(new TestAgentComponents(payloadSender: payloadSender, configuration: new MockConfiguration(exitSpanMinDuration: "0")));

        agent.Tracer.CaptureTransaction(TransactionName, TransactionType, t =>
        {
            WaitHelpers.SleepMinimum();
            t.CaptureSpan("SampleSpan1", "SampleSpanType",
                          span => { span.Context.Http = new Http {
                                        Url = "http://mysite.com", Method = "GET", StatusCode = 200
                                    }; }, isExitSpan: true);

            t.CaptureSpan("SampleSpan2", "SampleSpanType",
                          span =>
            {
                span.Context.Db = new Database {
                    Statement = "Select * from MyTable", Type = Database.TypeSql, Instance = "MyInstance"
                };
            }, isExitSpan: true);
        });

        payloadSender.Spans[0].Name.Should().Be("SampleSpan1");
        payloadSender.Spans[0].Context.Http.Url.Should().Be("http://mysite.com");
        payloadSender.Spans[0].Context.Http.Method.Should().Be("GET");
        payloadSender.Spans[0].Context.Http.StatusCode.Should().Be(200);
        payloadSender.Spans[0].Context.Destination.Address.Should().Be("mysite.com");
        payloadSender.Spans[0].Context.Destination.Port.Should().Be(UrlUtilsTests.DefaultHttpPort);

        payloadSender.Spans[1].Name.Should().Be("SampleSpan2");
        payloadSender.Spans[1].Context.Db.Statement.Should().Be("Select * from MyTable");
        payloadSender.Spans[1].Context.Db.Type.Should().Be(Database.TypeSql);
        payloadSender.Spans[1].Context.Db.Instance.Should().Be("MyInstance");
    }
        public async Task CancelledAsyncTask()
        {
            var payloadSender = new MockPayloadSender();
            var agent         = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            var cancellationTokenSource = new CancellationTokenSource();
            var token = cancellationTokenSource.Token;

            cancellationTokenSource.Cancel();

            await Assert.ThrowsAsync <OperationCanceledException>(async() =>
            {
                await agent.Tracer.CaptureTransaction(TransactionName, TransactionType,
                                                      async() =>
                {
                    // ReSharper disable once MethodSupportsCancellation, we want to delay before we throw the exception
                    await Task.Delay(SleepLength);
                    token.ThrowIfCancellationRequested();
                });
            });

            Assert.NotEmpty(payloadSender.Payloads);
            Assert.NotEmpty(payloadSender.Payloads[0].Transactions);

            Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name);
            Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type);

            Assert.True(payloadSender.Payloads[0].Transactions[0].Duration >= SleepLength);


            Assert.NotEmpty(payloadSender.Errors);
            Assert.NotEmpty(payloadSender.Errors[0].Errors);

            Assert.Equal("A task was canceled", payloadSender.Errors[0].Errors[0].Culprit);
            Assert.Equal("Task canceled", payloadSender.Errors[0].Errors[0].Exception.Message);
        }
Example #10
0
        public void AgentDisabledTransactionWithLambda()
        {
            var payloadSender = new MockPayloadSender();
            var configReader  = new MockConfigSnapshot(enabled: "false");

            using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender, config: configReader));
            var codeExecuted = false;

            agent.Tracer.CaptureTransaction("TestTransaction", "Test", transaction =>
            {
                codeExecuted = true;
                transaction.Should().NotBeOfType <Transaction>();
                transaction.Should().BeOfType <NoopTransaction>();

                // ReSharper disable AccessToDisposedClosure
                agent.Tracer.CurrentTransaction.Should().NotBeNull();
                agent.Tracer.CurrentTransaction.Should().Be(transaction);
            });

            codeExecuted.Should().BeTrue();
            payloadSender.SignalEndTransactions();
            payloadSender.WaitForTransactions();
            payloadSender.Transactions.Should().BeNullOrEmpty();
        }
Example #11
0
        /// <summary>
        /// Shared between ErrorOnTransaction and ErrorOnTransactionWithCulprit
        /// </summary>
        /// <param name="culprit">Culprit.</param>
        private void ErrorOnTransactionCommon(string culprit = null)
        {
            var transactionName  = "TestTransaction";
            var transacitonType  = "UnitTest";
            var exceptionMessage = "Foo!";
            var payloadSender    = new MockPayloadSender();
            var agent            = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            var transaction = agent.Tracer.StartTransaction(transactionName, transacitonType);

            Thread.Sleep(5);             //Make sure we have duration > 0
            try
            {
                throw new InvalidOperationException(exceptionMessage);
            }
            catch (Exception e)
            {
                if (string.IsNullOrEmpty(culprit))
                {
                    transaction.CaptureException(e);
                }
                else
                {
                    transaction.CaptureException(e, culprit);
                }
            }

            transaction.End();

            Assert.Single(payloadSender.Payloads);
            Assert.Single(payloadSender.Errors);
            Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message);
            Assert.Equal(exceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message);

            Assert.Equal(!string.IsNullOrEmpty(culprit) ? culprit : "PublicAPI-CaptureException", payloadSender.Errors[0].Errors[0].Culprit);
        }
        public void OnNextWithStartAndStop()
        {
            var logger        = new TestLogger();
            var payloadSender = new MockPayloadSender();
            var agent         = new ApmAgent(new TestAgentComponents(logger, payloadSender: payloadSender));

            StartTransaction(agent);
            var listener = HttpDiagnosticListener.New(agent);

            var request  = new HttpRequestMessage(HttpMethod.Get, "https://elastic.co");
            var response = new HttpResponseMessage(HttpStatusCode.OK);

            //Simulate Start
            listener.OnNext(new KeyValuePair <string, object>(StartEventKey(listener), new { Request = request }));
            //Simulate Stop
            listener.OnNext(new KeyValuePair <string, object>(StopEventKey(listener), new { Request = request, Response = response }));
            ProcessingRequestsCount(listener).Should().Be(0);

            var firstSpan = payloadSender.FirstSpan;

            firstSpan.Should().NotBeNull();
            firstSpan.Context.Http.Url.Should().BeEquivalentTo(request.RequestUri.AbsoluteUri);
            firstSpan.Context.Http.Method.Should().Be(HttpMethod.Get.Method);
        }
Example #13
0
        public async Task HttpCallWithW3CActivityFormar()
        {
            Activity.DefaultIdFormat = ActivityIdFormat.W3C;

            var mockPayloadSender = new MockPayloadSender();

            using var localServer = new LocalServer();
            using var agent       = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender));
            agent.Subscribe(new HttpDiagnosticsSubscriber());
            await agent.Tracer.CaptureTransaction("Test", "Test", async() =>
            {
                var httpClient = new HttpClient();
                try
                {
                    await httpClient.GetAsync(localServer.Uri);
                }
                catch
                {
                    //ignore - we don't care about the result
                }
            });

            mockPayloadSender.Spans.Should().HaveCount(1);
        }
Example #14
0
        /// <summary>
        /// Asserts on 1 async transaction and 1 error
        /// </summary>
        private async Task <MockPayloadSender> AssertWith1TransactionAnd1ErrorAsync(Func <ApmAgent, Task> func)
        {
            var payloadSender = new MockPayloadSender();
            var agent         = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            await func(agent);

            Assert.NotEmpty(payloadSender.Payloads);
            Assert.NotEmpty(payloadSender.Payloads[0].Transactions);

            Assert.Equal(TransactionName, payloadSender.Payloads[0].Transactions[0].Name);
            Assert.Equal(TransactionType, payloadSender.Payloads[0].Transactions[0].Type);

            var duration = payloadSender.Payloads[0].Transactions[0].Duration;

            WaitHelpers.AssertMinimumSleepLength(duration);

            Assert.NotEmpty(payloadSender.Errors);
            Assert.NotEmpty(payloadSender.Errors[0].Errors);

            Assert.Equal(typeof(InvalidOperationException).FullName, payloadSender.Errors[0].Errors[0].Exception.Type);
            Assert.Equal(ExceptionMessage, payloadSender.Errors[0].Errors[0].Exception.Message);
            return(payloadSender);
        }
Example #15
0
        public void CreateSubSpan()
        {
            var payloadSender = new MockPayloadSender();

            StartTransactionAndSpanWithSubSpan(payloadSender, s => { });
        }
Example #16
0
        public void ErrorShouldContainTransactionData(bool isSampled, bool captureOnSpan, bool captureAsError)
        {
            var payloadSender        = new MockPayloadSender();
            var expectedErrorContext = new Context();

            expectedErrorContext.InternalLabels.Value.InnerDictionary["one"]        = 1;
            expectedErrorContext.InternalLabels.Value.InnerDictionary["twenty two"] = "22";
            expectedErrorContext.InternalLabels.Value.InnerDictionary["true"]       = true;

            ITransaction      capturedTransaction            = null;
            IExecutionSegment errorCapturingExecutionSegment = null;
            var mockConfig = new MockConfigSnapshot(transactionSampleRate: isSampled ? "1" : "0");

            using (var agent = new ApmAgent(new TestAgentComponents(config: mockConfig, payloadSender: payloadSender)))
            {
                agent.Tracer.CaptureTransaction(TestTransaction, CustomTransactionTypeForTests, transaction =>
                {
                    capturedTransaction = transaction;

                    foreach (var item in expectedErrorContext.InternalLabels.Value.MergedDictionary)
                    {
                        transaction.Context.InternalLabels.Value.MergedDictionary[item.Key] = item.Value;
                    }
                    ISpan span = null;
                    if (captureOnSpan)
                    {
                        span = transaction.StartSpan(TestSpan1, ApiConstants.TypeExternal);
                        errorCapturingExecutionSegment = span;
                    }
                    else
                    {
                        errorCapturingExecutionSegment = transaction;
                    }

                    if (captureAsError)
                    {
                        errorCapturingExecutionSegment.CaptureError("Test error message", "Test error culprit", new StackTrace(true).GetFrames());
                    }
                    else
                    {
                        errorCapturingExecutionSegment.CaptureException(new TestException("test exception"));
                    }

                    // Immutable snapshot of the context should be captured instead of reference to a mutable object
                    // transaction.Context.Labels["three hundred thirty three"] = "333";

                    span?.End();
                });
            }

            payloadSender.WaitForErrors();
            payloadSender.Errors.Count.Should().Be(1);
            payloadSender.FirstError.Transaction.IsSampled.Should().Be(isSampled);
            payloadSender.FirstError.Transaction.Type.Should().Be(CustomTransactionTypeForTests);
            payloadSender.FirstError.TransactionId.Should().Be(capturedTransaction.Id);
            payloadSender.FirstError.TraceId.Should().Be(capturedTransaction.TraceId);
            payloadSender.FirstError.ParentId.Should()
            .Be(errorCapturingExecutionSegment.IsSampled ? errorCapturingExecutionSegment.Id : capturedTransaction.Id);

            payloadSender.FirstError.Transaction.IsSampled.Should().Be(isSampled);
            if (isSampled)
            {
                payloadSender.FirstError.Context.Should().NotBeNull().And.BeEquivalentTo(expectedErrorContext);
            }
            else
            {
                payloadSender.FirstError.Context.Should().BeNull();
            }
        }
Example #17
0
        public void destination_properties_set_manually_have_precedence_over_automatically_deduced()
        {
            var          url           = new Uri("http://elastic.co");
            const string manualAddress = "manual.address";
            const int    manualPort    = 1234;
            var          payloadSender = new MockPayloadSender();

            using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender)))
            {
                agent.Tracer.CaptureTransaction("test TX name", "test TX type", tx =>
                {
                    tx.CaptureSpan("manually set destination address", "test_span_type", span =>
                    {
                        span.Context.Destination = new Destination {
                            Address = manualAddress
                        };
                        span.Context.Http = new Http {
                            Method = "PUT", Url = url.ToString()
                        };
                    });
                    tx.CaptureSpan("manually set destination port", "test_span_type", span =>
                    {
                        span.Context.Destination = new Destination {
                            Port = manualPort
                        };
                        span.Context.Http = new Http {
                            Method = "PUT", Url = url.ToString()
                        };
                    });
                    tx.CaptureSpan("manually set destination address to null", "test_span_type", span =>
                    {
                        span.Context.Destination = new Destination {
                            Address = null
                        };
                        span.Context.Http = new Http {
                            Method = "PUT", Url = url.ToString()
                        };
                    });
                    tx.CaptureSpan("manually set destination port to null", "test_span_type", span =>
                    {
                        span.Context.Destination = new Destination {
                            Port = null
                        };
                        span.Context.Http = new Http {
                            Method = "PUT", Url = url.ToString()
                        };
                    });
                });
            }

            payloadSender.WaitForSpans(count: 4);
            var manualAddressSpan = payloadSender.Spans.Single(s => s.Name == "manually set destination address");

            manualAddressSpan.Context.Destination.Address.Should().Be(manualAddress);
            manualAddressSpan.Context.Destination.Port.Should().Be(url.Port);

            var manualPortSpan = payloadSender.Spans.Single(s => s.Name == "manually set destination port");

            manualPortSpan.Context.Destination.Address.Should().Be(url.Host);
            manualPortSpan.Context.Destination.Port.Should().Be(manualPort);

            var nullAddressSpan = payloadSender.Spans.Single(s => s.Name == "manually set destination address to null");

            nullAddressSpan.Context.Destination.Address.Should().BeNull();
            nullAddressSpan.Context.Destination.Port.Should().Be(url.Port);

            var nullPortSpan = payloadSender.Spans.Single(s => s.Name == "manually set destination port to null");

            nullPortSpan.Context.Destination.Address.Should().Be(url.Host);
            nullPortSpan.Context.Destination.Port.Should().BeNull();
        }
Example #18
0
        public async Task Capture_Redis_Commands_On_Transaction()
        {
            var containerBuilder = new TestcontainersBuilder <RedisTestcontainer>()
                                   .WithDatabase(new RedisTestcontainerConfiguration());

            await using var container = containerBuilder.Build();
            await container.StartAsync();

            var connection = await ConnectionMultiplexer.ConnectAsync(container.ConnectionString);

            var count = 0;

            while (!connection.IsConnected)
            {
                if (count < 5)
                {
                    count++;
                    await Task.Delay(500);
                }
                else
                {
                    throw new Exception("Could not connect to redis for integration test");
                }
            }

            var payloadSender    = new MockPayloadSender();
            var transactionCount = 2;

            using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));
            connection.UseElasticApm(agent);

            for (var i = 0; i < transactionCount; i++)
            {
                await agent.Tracer.CaptureTransaction("Set and Get String", ApiConstants.TypeDb, async() =>
                {
                    var database = connection.GetDatabase();
                    await database.StringSetAsync($"string{i}", i);
                    await database.StringGetAsync($"string{i}");
                    await database.StringSetAsync($"string{i}", i);
                    await database.StringGetAsync($"string{i}");

                    // fire and forget commands may not end up being captured before transaction end is
                    // called and profiling session is finished
                    await database.StringSetAsync($"string{i}", i, flags: CommandFlags.FireAndForget);
                    await database.StringGetAsync($"string{i}", CommandFlags.FireAndForget);
                });
            }

            var transactions = payloadSender.Transactions;

            transactions.Should().HaveCount(transactionCount);

            var minSpansPerTransaction = 4;

            payloadSender.Spans.Should().HaveCountGreaterOrEqualTo(transactionCount * minSpansPerTransaction);

            foreach (var transaction in transactions)
            {
                payloadSender.Spans.Count(s => s.TransactionId == transaction.Id).Should().BeGreaterOrEqualTo(minSpansPerTransaction);
            }

            await container.StopAsync();
        }
Example #19
0
        public async Task Capture_Redis_Commands_On_Span()
        {
            var containerBuilder = new TestcontainersBuilder <RedisTestcontainer>()
                                   .WithDatabase(new RedisTestcontainerConfiguration());

            await using var container = containerBuilder.Build();
            await container.StartAsync();

            var connection = await ConnectionMultiplexer.ConnectAsync(container.ConnectionString);

            var count = 0;

            while (!connection.IsConnected)
            {
                if (count < 5)
                {
                    count++;
                    await Task.Delay(500);
                }
                else
                {
                    throw new Exception("Could not connect to redis for integration test");
                }
            }

            var payloadSender    = new MockPayloadSender();
            var transactionCount = 2;

            using var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));
            connection.UseElasticApm(agent);

            var database = connection.GetDatabase();

            for (var i = 0; i < transactionCount; i++)
            {
                await agent.Tracer.CaptureTransaction($"transaction {i}", ApiConstants.TypeDb, async t =>
                {
                    // span 1
                    await database.StringSetAsync($"string{i}", i);

                    // span 2
                    await database.StringGetAsync($"string{i}");

                    // span 3
                    await t.CaptureSpan($"parent span {i}", ApiConstants.TypeDb, async() =>
                    {
                        // spans 4,5,6,7
                        await database.StringSetAsync($"string{i}", i);
                        await database.StringGetAsync($"string{i}");
                        await database.StringSetAsync($"string{i}", i);
                        await database.StringGetAsync($"string{i}");
                    });
                });
            }

            var transactions = payloadSender.Transactions;

            transactions.Should().HaveCount(transactionCount);

            var spansPerParentSpan  = 4;
            var topLevelSpans       = 3;
            var spansPerTransaction = spansPerParentSpan + topLevelSpans;

            payloadSender.Spans.Should().HaveCount(transactionCount * spansPerTransaction);

            foreach (var transaction in transactions)
            {
                var transactionSpans = payloadSender.Spans
                                       .Where(s => s.TransactionId == transaction.Id)
                                       .ToList();

                transactionSpans.Should().HaveCount(spansPerTransaction);

                var parentSpans = transactionSpans.Where(s => s.ParentId == s.TransactionId).ToList();
                parentSpans.Should().HaveCount(topLevelSpans);

                var parentSpanId = parentSpans.OrderByDescending(s => s.Timestamp).First().Id;
                transactionSpans.Count(s => s.ParentId == parentSpanId).Should().Be(spansPerParentSpan);
            }

            await container.StopAsync();
        }
        public void CompressionOnParentSpan()
        {
            var payloadSender = new MockPayloadSender();

            using (var agent = new ApmAgent(new TestAgentComponents(apmServerInfo: MockApmServerInfo.Version80, payloadSender: payloadSender,
                                                                    configuration: new MockConfiguration(spanCompressionEnabled: "true", spanCompressionExactMatchMaxDuration: "5s",
                                                                                                         exitSpanMinDuration: "0"))))
            {
                agent.Tracer.CaptureTransaction("Foo", "Bar", t =>
                {
                    t.CaptureSpan("foo1", "bar", span1 =>
                    {
                        for (var i = 0; i < 10; i++)
                        {
                            span1.CaptureSpan("Select * From Table1", ApiConstants.TypeDb, (s) =>
                            {
                                s.Context.Db = new Database()
                                {
                                    Type = "mssql", Instance = "01"
                                };
                            }, ApiConstants.SubtypeMssql, isExitSpan: true);
                        }

                        span1.CaptureSpan("foo2", "randomSpan", () => { });


                        for (var i = 0; i < 10; i++)
                        {
                            span1.CaptureSpan("Select * From Table2", ApiConstants.TypeDb, (s2) =>
                            {
                                s2.Context.Db = new Database()
                                {
                                    Type = "mssql", Instance = "01"
                                };
                            }, ApiConstants.SubtypeMssql, isExitSpan: true);
                        }
                    });
                });
            }

            payloadSender.Transactions.Should().HaveCount(1);
            payloadSender.Spans.Should().HaveCount(4);

            (payloadSender.Spans[3] as Span) !.Composite.Should().BeNull();
            payloadSender.Spans[3].Name.Should().Be("foo1");

            (payloadSender.Spans[0] as Span) !.Composite.Should().NotBeNull();
            (payloadSender.Spans[0] as Span) !.Composite.Count.Should().Be(10);
            (payloadSender.Spans[0] as Span) !.Composite.CompressionStrategy = "exact_match";
            payloadSender.Spans[0].Name.Should().Be("Select * From Table1");

            payloadSender.Spans[1].Name.Should().Be("foo2");
            (payloadSender.Spans[1] as Span) !.Composite.Should().BeNull();

            (payloadSender.Spans[2] as Span) !.Composite.Should().NotBeNull();
            (payloadSender.Spans[2] as Span) !.Composite.Count.Should().Be(10);
            (payloadSender.Spans[2] as Span) !.Composite.CompressionStrategy = "exact_match";
            payloadSender.Spans[2].Name.Should().Be("Select * From Table2");


            payloadSender.Spans[0].ParentId.Should().Be(payloadSender.Spans[3].Id);
            payloadSender.Spans[1].ParentId.Should().Be(payloadSender.Spans[3].Id);
            payloadSender.Spans[1].ParentId.Should().Be(payloadSender.Spans[3].Id);
        }
Example #21
0
        public async Task BasicGrpcTest(bool withDiagnosticSource)
        {
            var payloadSender = new MockPayloadSender();

            using var apmAgent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender));

            IHost sampleAppHost;

            if (withDiagnosticSource)
            {
                sampleAppHost = new SampleAppHostBuilder().BuildHost();
                apmAgent.Subscribe(new AspNetCoreDiagnosticSubscriber());
            }
            else
            {
                sampleAppHost = new SampleAppHostBuilder().BuildHostWithMiddleware(apmAgent);
            }

            apmAgent.Subscribe(new GrpcClientDiagnosticSubscriber(), new HttpDiagnosticsSubscriber());

            await sampleAppHost.StartAsync();

            AppContext.SetSwitch(
                "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

            var channel = GrpcChannel.ForAddress(SampleAppHostBuilder.SampleAppUrl);
            var client  = new Greeter.GreeterClient(channel);

            await apmAgent.Tracer.CaptureTransaction("SampleCall", "test", async() =>
            {
                var response = await client.SayHelloAsync(
                    new HelloRequest {
                    Name = "World"
                });

                Debug.WriteLine(response.Message);
            });

            payloadSender.Transactions.Should().HaveCount(2);

            payloadSender.Transactions.Should()
            .Contain(transaction => transaction.Result == "OK" &&
                     transaction.Name == $"/{Greeter.Descriptor.FullName}/{nameof(client.SayHello)}" &&
                     transaction.Outcome == Outcome.Success);

            //Make sure all spans are collected
            Thread.Sleep(500);

            payloadSender.Spans.Should().HaveCountGreaterThan(1);
            payloadSender.Spans.Should()
            .Contain(span => span.Subtype == ApiConstants.SubTypeGrpc &&
                     span.Outcome == Outcome.Success &&
                     span.Name == $"/{Greeter.Descriptor.FullName}/{nameof(client.SayHello)}" &&
                     span.Context.Destination.Address == "localhost" &&
                     span.Context.Destination.Port == SampleAppHostBuilder.SampleAppPort &&
                     span.Context.Destination.Service.Type == "external" &&
                     span.Context.Destination.Service.Name == SampleAppHostBuilder.SampleAppUrl &&
                     span.Context.Destination.Service.Resource == $"localhost:{SampleAppHostBuilder.SampleAppPort}"
                     );

            await sampleAppHost.StopAsync();
        }
Example #22
0
        public void ChangeTransactionContextAfterError()
        {
            var mockPayloadSender = new MockPayloadSender();

            using var agent = new ApmAgent(new TestAgentComponents(payloadSender: mockPayloadSender));

            agent.Tracer.CaptureTransaction("Test", "Test", t =>
            {
                t.Context.Request = new Request("GET", new Url {
                    Full = "http://localhost", Protocol = "http", Search = "abc"
                })
                {
                    Body = "abc", Headers = new Dictionary <string, string> {
                        { "header1", "headerValue" }
                    }
                };
                t.Context.Response = new Response {
                    StatusCode = 404, Finished = false
                };

                t.SetLabel("foo", "bar");
                // Let's capture an error
                t.CaptureError("Test Error", "Test", new StackTrace().GetFrames());

                // Let's change CurrentTransaction.Context after the error is captured
                t.Context.Request.Method             = "PUT";
                t.Context.Request.Body               = "cde";
                t.Context.Request.Headers["header2"] = "headerValue";
                t.Context.Request.Url.Full           = "http://elastic.co";
                t.Context.Request.Url.Protocol       = "tcp";
                t.Context.Request.Url.Search         = "cde";
                t.Context.Response.StatusCode        = 500;
                t.Context.Response.Finished          = true;
                t.Context.InternalLabels.Value.InnerDictionary["foo"].Value.Should().Be("bar");

                // Asserts on the captured error
                mockPayloadSender.WaitForErrors();
                mockPayloadSender.FirstError.Should().NotBeNull("first error should not be null");
                mockPayloadSender.FirstError.Context.Should().NotBeNull("context should not be null");
                mockPayloadSender.FirstError.Context.Request.Method.Should().Be("GET");
                mockPayloadSender.FirstError.Context.Request.Body.Should().Be("abc");
                mockPayloadSender.FirstError.Context.Request.Headers.Count.Should().Be(1);
                mockPayloadSender.FirstError.Context.Request.Headers["header1"].Should().Be("headerValue");
                mockPayloadSender.FirstError.Context.Request.Url.Full.Should().Be("http://localhost");
                mockPayloadSender.FirstError.Context.Request.Url.Protocol.Should().Be("http");
                mockPayloadSender.FirstError.Context.Request.Url.Search.Should().Be("abc");
                mockPayloadSender.FirstError.Context.Response.StatusCode.Should().Be(404);
                mockPayloadSender.FirstError.Context.Response.Finished.Should().BeFalse();
                mockPayloadSender.FirstError.Context.InternalLabels.Value.InnerDictionary["foo"].Value.Should().Be("bar");
                mockPayloadSender.FirstError.Context.Response.Headers.Should().BeNull();
            });

            // Asserts on the captured transaction
            mockPayloadSender.WaitForTransactions();
            mockPayloadSender.FirstTransaction.Context.Request.Method.Should().Be("PUT");
            mockPayloadSender.FirstTransaction.Context.Request.Body.Should().Be("cde");
            mockPayloadSender.FirstTransaction.Context.Request.Headers.Count.Should().Be(2);
            mockPayloadSender.FirstTransaction.Context.Request.Headers["header1"].Should().Be("headerValue");
            mockPayloadSender.FirstTransaction.Context.Request.Headers["header2"].Should().Be("headerValue");
            mockPayloadSender.FirstTransaction.Context.Request.Url.Full.Should().Be("http://elastic.co");
            mockPayloadSender.FirstTransaction.Context.Request.Url.Protocol.Should().Be("tcp");
            mockPayloadSender.FirstTransaction.Context.Request.Url.Search.Should().Be("cde");
            mockPayloadSender.FirstTransaction.Context.Response.StatusCode.Should().Be(500);
            mockPayloadSender.FirstTransaction.Context.Response.Finished.Should().BeTrue();
            mockPayloadSender.FirstTransaction.Context.InternalLabels.Value.InnerDictionary["foo"].Value.Should().Be("bar");
            mockPayloadSender.FirstTransaction.Context.Response.Headers.Should().BeNull();
        }
        public void MultipleDroppedSpanTest()
        {
            var payloadSender = new MockPayloadSender();

            using (var agent = new ApmAgent(new TestAgentComponents(payloadSender: payloadSender,
                                                                    configuration: new MockConfiguration(transactionMaxSpans: "1"))))
            {
                var transaction = agent.Tracer.StartTransaction("foo", "test");
                //This is the span which won't be dropped
                transaction.CaptureSpan("fooSpan", "test", () => { });

                //Next spans will be dropped
                var span1 = transaction.StartSpan("foo", "bar", isExitSpan: true);
                span1.Context.Http = new Http {
                    Method = "GET", StatusCode = 200, Url = "https://foo.bar"
                };
                span1.Duration = 100;
                span1.End();

                var span2 = transaction.StartSpan("foo", "bar", isExitSpan: true);
                span2.Context.Http = new Http {
                    Method = "GET", StatusCode = 200, Url = "https://foo.bar"
                };
                span2.Duration = 150;
                span2.End();

                var span3 = transaction.StartSpan("foo", "bar", isExitSpan: true);
                span3.Context.Http = new Http {
                    Method = "GET", StatusCode = 400, Url = "https://foo.bar"
                };
                span3.Outcome  = Outcome.Failure;
                span3.Duration = 50;
                span3.End();

                var span4 = transaction.StartSpan("foo", "bar", isExitSpan: true);
                span4.Context.Http = new Http {
                    Method = "GET", StatusCode = 400, Url = "https://foo2.bar"
                };
                span4.Duration = 15;
                span4.End();

                for (var i = 0; i < 50; i++)
                {
                    var span5 = transaction.StartSpan("foo", "bar", isExitSpan: true);
                    span5.Context.Destination = new Destination {
                        Service = new Destination.DestinationService {
                            Resource = "mysql"
                        }
                    };
                    span5.Context.Db = new Database {
                        Instance = "instance1", Type = "mysql", Statement = "Select Foo From Bar"
                    };
                    span5.Duration = 50;
                    span5.End();
                }

                transaction.End();
            }

            payloadSender.Spans.Should().HaveCount(1);

            payloadSender.FirstTransaction.DroppedSpanStats.Should().NotBeNullOrEmpty();
            payloadSender.FirstTransaction.DroppedSpanStats.Should().HaveCount(4);

            payloadSender.FirstTransaction.DroppedSpanStats.Should()
            .Contain(n => n.Outcome == Outcome.Success &&
                     n.DurationCount == 2 && Math.Abs(n.DurationSumUs - 250) < 1 && n.DestinationServiceResource == "foo.bar:443");

            payloadSender.FirstTransaction.DroppedSpanStats.Should()
            .Contain(n => n.Outcome == Outcome.Failure &&
                     n.DurationCount == 1 && Math.Abs(n.DurationSumUs - 50) < 1 && n.DestinationServiceResource == "foo.bar:443");

            payloadSender.FirstTransaction.DroppedSpanStats.Should()
            .Contain(n => n.Outcome == Outcome.Success &&
                     n.DurationCount == 1 && Math.Abs(n.DurationSumUs - 15) < 1 && n.DestinationServiceResource == "foo2.bar:443");

            payloadSender.FirstTransaction.DroppedSpanStats.Should()
            .Contain(n => n.Outcome == Outcome.Success &&
                     n.DurationCount == 50 && Math.Abs(n.DurationSumUs - 50 * 50) < 1 && n.DestinationServiceResource == "mysql");
        }
 public DiagnosticListenerTest(WebApplicationFactory <Startup> factory)
 {
     _agent           = new ApmAgent(new TestAgentComponents());
     _capturedPayload = _agent.PayloadSender as MockPayloadSender;
     _client          = Helper.GetClientWithoutDiagnosticListeners(_agent, factory);
 }