Exemple #1
0
        public void ShouldPassOneInputToOneOutput()
        {
            // Setup
            Mock <IHealthReporter>          healthReporterMock = new Mock <IHealthReporter>();
            Mock <IOutput>                  mockOutput         = new Mock <IOutput>();
            DiagnosticPipelineConfiguration settings           = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = 1000
            };

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(mockOutput.Object, null) },
                           settings))
                {
                    // Execrise
                    unitTestInput.SendMessage("Test information");
                }

            // Verify
            mockOutput.Verify(o => o.SendEventsAsync(It.Is <IReadOnlyCollection <EventData> >(c => c.Count == 1),
                                                     It.IsAny <long>(), It.IsAny <CancellationToken>()), Times.Exactly(1));
        }
Exemple #2
0
        public async Task UsableIfBufferOverflowOccurs()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();
            UnitTestOutput         unitTestOutput     = new UnitTestOutput();

            unitTestOutput.SendEventsDelay = TimeSpan.FromMilliseconds(100);
            DiagnosticPipelineConfiguration settings = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = 1000,
                PipelineBufferSize            = 1,
                MaxConcurrency    = 1,
                MaxEventBatchSize = 1
            };
            const int TestBatchSize = 6;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(unitTestOutput, null) },
                           settings))
                {
                    // Six events in quick succession will cause a buffer overflow
                    // (we have a buffer of 1 set for the pipeline, but the pipeline has 3 blocks, so the actual buffer space is 3).
                    // There should be no ill effects from that on the input--not catching any exceptions.
                    for (int i = 0; i < TestBatchSize; i++)
                    {
                        unitTestInput.SendMessage($"Message {i}");
                    }

                    // Wait for the pipeline to drain (use ten times batch delay time for extra padding).
                    await Task.Delay(TimeSpan.FromMilliseconds(TestBatchSize * (unitTestOutput.SendEventsDelay.TotalMilliseconds + settings.MaxBatchDelayMsec * 10)));

                    // At least one message should get to the output.
                    Assert.True(unitTestOutput.CallCount > 0);

                    // We should get a warning about throttling
                    healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Throttling)), Times.AtLeastOnce());

                    // Pipeline should still be usable after this. Let's try to send a message through it.
                    unitTestOutput.CallCount = 0;
                    healthReporterMock.ResetCalls();
                    unitTestInput.SendMessage("Final message");

                    // Wait for the message to be processed (use ten times batch delay time for extra padding)
                    await Task.Delay(TimeSpan.FromMilliseconds(unitTestOutput.SendEventsDelay.TotalMilliseconds + settings.MaxBatchDelayMsec * 10));

                    // The message should have come through.
                    Assert.Equal(1, unitTestOutput.CallCount);

                    // There should be no new warnings or errors
                    healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.IsAny <string>()), Times.Never());
                    healthReporterMock.Verify(o => o.ReportProblem(It.IsAny <string>(), It.IsAny <string>()), Times.Never());
                }
        }
Exemple #3
0
        public void UsableIfBufferOverflowOccurs()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();
            var            deterministicTaskScheduler = new DeterministicTaskScheduler();
            UnitTestOutput unitTestOutput             = new UnitTestOutput();
            DiagnosticPipelineConfiguration settings  = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = 5000,
                PipelineBufferSize            = 1,
                MaxConcurrency    = 1,
                MaxEventBatchSize = 1
            };
            const int TestBatchSize = 6;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(unitTestOutput, null) },
                           settings,
                           disposeDependencies: false,
                           taskScheduler: deterministicTaskScheduler))
                {
                    // Six events in quick succession will cause a buffer overflow
                    // (we have a buffer of 1 set for the pipeline, but the pipeline has 3 blocks, so the actual buffer space is 3).
                    // There should be no ill effects from that on the input--not catching any exceptions.
                    for (int i = 0; i < TestBatchSize; i++)
                    {
                        unitTestInput.SendMessage($"Message {i}");
                    }

                    // Wait for the pipeline to drain
                    deterministicTaskScheduler.RunTasksUntilIdle();

                    Assert.True(unitTestOutput.CallCount > 0, "At least one message should get to the output");

                    // We should get a warning about throttling
                    healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Throttling)), Times.AtLeastOnce());

                    // Pipeline should still be usable after this. Let's try to send a message through it.
                    unitTestOutput.CallCount = 0;
                    healthReporterMock.ResetCalls();
                    unitTestInput.SendMessage("Final message");

                    // Give the pipeline a chance to process the message
                    deterministicTaskScheduler.RunTasksUntilIdle();

                    // The message should have come through.
                    Assert.Equal(1, unitTestOutput.CallCount);

                    // There should be no new warnings or errors
                    healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.IsAny <string>()), Times.Never());
                    healthReporterMock.Verify(o => o.ReportProblem(It.IsAny <string>(), It.IsAny <string>()), Times.Never());
                }
        }
Exemple #4
0
        public void UsableIfExceptionInOutputSpecificFilterOccurs()
        {
            Mock <IHealthReporter>          healthReporterMock = new Mock <IHealthReporter>();
            UnitTestOutput                  unitTestOutput     = new UnitTestOutput();
            DiagnosticPipelineConfiguration settings           = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = 10000,
                MaxEventBatchSize             = 2
            };
            UnitTestFilter unitTestFilter = new UnitTestFilter();

            unitTestFilter.EvaluationFailureCondition = "Trouble == true";
            const int TestEventCount = 6;
            DateTime  pipelineDisposalStart;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(unitTestOutput, new IFilter[] { unitTestFilter }) },
                           settings))
                {
                    // Half of the events should cause filtering to fail with an exception
                    for (int i = 0; i < TestEventCount; i++)
                    {
                        if (i % 2 == 0)
                        {
                            unitTestInput.SendData(new Dictionary <string, object> {
                                ["Trouble"] = true
                            });
                        }
                        else
                        {
                            unitTestInput.SendMessage("Hi!");
                        }
                    }

                    pipelineDisposalStart = DateTime.Now;
                }

            // We should have got good events and warnings about bad events
            DateTime pipelineDisposalEnd = DateTime.Now;

            // We should have got good events and warnings about bad events
            Assert.True(TestEventCount / 2 == unitTestOutput.EventCount,
                        $"Events missing: expected: {TestEventCount / 2}, " +
                        $"actual: {unitTestOutput.EventCount}, " +
                        $"filter invocations: {unitTestFilter.CallCount}, " +
                        $"pipeline disposal time: {(pipelineDisposalEnd - pipelineDisposalStart).TotalMilliseconds} msec");

            healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Filtering)), Times.Exactly(TestEventCount / 2));
        }
Exemple #5
0
        private static DiagnosticPipeline CreateEventFlow(string[] args)
        {
            // Create configuration instance to access configuration information for EventFlow pipeline
            // To learn about common configuration sources take a peek at https://github.com/aspnet/MetaPackages/blob/master/src/Microsoft.AspNetCore/WebHost.cs (CreateDefaultBuilder method).
            var configBuilder = new ConfigurationBuilder()
                                .AddEnvironmentVariables();

            var devEnvironmentVariable = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            var isDevelopment          = string.IsNullOrEmpty(devEnvironmentVariable) ||
                                         devEnvironmentVariable.ToLower() == "development";

            if (isDevelopment)
            {
                configBuilder.AddUserSecrets <Program>();
            }
            else
            {
                configBuilder.SetBasePath(Directory.GetCurrentDirectory());
                configBuilder.AddJsonFile("appsettings.json", false, false);
            }

            if (args != null)
            {
                configBuilder.AddCommandLine(args);
            }
            var config = configBuilder.Build();

            // SEE https://github.com/Azure/diagnostics-eventflow#http
            var httpConfig     = config.GetSection("HttpEventSinkConfig");
            var filterConfig   = config.GetSection("FilterConfig");
            var pipelineConfig = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 5000,  // Specifies the maximum time that events are held in a batch before the batch gets pushed through the pipeline to filters and outputs. The batch is pushed down when it reaches the maxEventBatchSize, or its oldest event has been in the batch for more than maxBatchDelayMsec milliseconds.
                PipelineCompletionTimeoutMsec = 10000, // Specifies the timeout to wait for the pipeline to shutdown and clean up. The shutdown process starts when the DiagnosePipeline object is disposed, which usually happens on application exit.
                MaxEventBatchSize             = 50,    // Specifies the maximum number of events to be batched before the batch gets pushed through the pipeline to filters and outputs. The batch is pushed down when it reaches the maxEventBatchSize, or its oldest event has been in the batch for more than maxBatchDelayMsec milliseconds.
                PipelineBufferSize            = 1000   // Specifies how many events the pipeline can buffer if the events cannot flow through the pipeline fast enough. This buffer protects loss of data in cases where there is a sudden burst of data.
            };
            var healthReporter = new CsvHealthReporter(new CsvHealthReporterConfiguration());
            var aiInput        = new ApplicationInsightsInputFactory().CreateItem(null, healthReporter);
            var aiFilters      = new CustomFilterFactory().CreateItem(filterConfig, healthReporter);
            var inputs         = new IObservable <EventData>[] { aiInput };
            var filters        = new IFilter[] { aiFilters };

            var sinks = new EventSink[]
            {
                new EventSink(new StdOutput(healthReporter), null),
                new EventSink(new HttpOutput(httpConfig, healthReporter), filters) // again, see https://github.com/Azure/diagnostics-eventflow#http
            };

            return(new DiagnosticPipeline(healthReporter, inputs, filters, sinks, pipelineConfig, disposeDependencies: true));
        }
Exemple #6
0
        public async Task WarnsAboutThrottlingIfOneSinkIsSlow()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();

            UnitTestOutput fastOutput = new UnitTestOutput();
            UnitTestOutput slowOutput = new UnitTestOutput();

            slowOutput.SendEventsDelay = TimeSpan.FromMilliseconds(50);

            const int InputBufferSize = 10;
            const int BurstCount      = 100;

            DiagnosticPipelineConfiguration settings = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineBufferSize            = InputBufferSize,
                MaxEventBatchSize             = 4,
                MaxConcurrency                = 2,
                PipelineCompletionTimeoutMsec = 5000
            };

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(fastOutput, null), new EventSink(slowOutput, null) },
                           settings))
                {
                    for (int burst = 0; burst < BurstCount; burst++)
                    {
                        // Each burst fills the input buffer
                        for (int i = 0; i < InputBufferSize; i++)
                        {
                            unitTestInput.SendMessage($"{burst}--{i}");
                        }

                        // Give the pipeline some time to process events--the fast output will keep up, the slow one, certainly not
                        bool eventsReceived = await TaskUtils.PollWaitAsync(() => fastOutput.EventCount == (burst + 1) *InputBufferSize, TimeSpan.FromSeconds(5));

                        Assert.True(eventsReceived);
                    }

                    // Slow output should have received some, but not all events
                    Assert.InRange(slowOutput.EventCount, 1, BurstCount * InputBufferSize - 1);
                }

            healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Throttling)),
                                      Times.AtLeastOnce());
        }
Exemple #7
0
        public async Task CanDisposePipelineStuckInAnOutput()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();
            UnitTestOutput         unitTestOutput     = new UnitTestOutput();

            unitTestOutput.SendEventsDelay            = TimeSpan.MaxValue;
            unitTestOutput.DisregardCancellationToken = true;

            const int CompletionTimeoutMsec          = 1000;
            DiagnosticPipelineConfiguration settings = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = CompletionTimeoutMsec,
                MaxEventBatchSize             = 1,
                PipelineBufferSize            = 1,
                MaxConcurrency = 1
            };
            Stopwatch stopwatch;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(unitTestOutput, null) },
                           settings))
                {
                    // Saturate the pipeline
                    for (int i = 0; i < 10; i++)
                    {
                        unitTestInput.SendMessage("Hi!");
                    }

                    // Wait a little bit for the pipeline to process messages and get stuck in the output.
                    await Task.Delay(TimeSpan.FromMilliseconds(100));

                    stopwatch = Stopwatch.StartNew();
                }

            stopwatch.Stop();

            // We should have received not sent any data successfully
            Assert.Equal(0, unitTestOutput.CallCount);

            // Ensure the pipeline stops within the timeout (plus some padding)
            Assert.InRange(stopwatch.ElapsedMilliseconds, 0, CompletionTimeoutMsec + 200);
        }
Exemple #8
0
        public async Task UsableIfExceptionInOutputSpecificFilterOccurs()
        {
            Mock <IHealthReporter>          healthReporterMock = new Mock <IHealthReporter>();
            UnitTestOutput                  unitTestOutput     = new UnitTestOutput();
            DiagnosticPipelineConfiguration settings           = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = 1000,
                MaxEventBatchSize             = 2
            };
            UnitTestFilter unitTestFilter = new UnitTestFilter();

            unitTestFilter.EvaluationFailureCondition = "Trouble == true";
            const int TestEventCount = 6;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(unitTestOutput, new IFilter[] { unitTestFilter }) },
                           settings))
                {
                    // Half of the events should cause filtering to fail with an exception
                    for (int i = 0; i < TestEventCount; i++)
                    {
                        if (i % 2 == 0)
                        {
                            unitTestInput.SendData(new Dictionary <string, object> {
                                ["Trouble"] = true
                            });
                        }
                        else
                        {
                            unitTestInput.SendMessage("Hi!");
                        }
                    }

                    // Wait for the pipeline to drain.
                    await Task.Delay(TimeSpan.FromMilliseconds(300));

                    // We should have got good events and warnings about bad events
                    Assert.Equal(TestEventCount / 2, unitTestOutput.EventCount);
                    healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Filtering)), Times.Exactly(TestEventCount / 2));
                }
        }
Exemple #9
0
        public void CanDisposePipelineStuckInAFilter()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();
            UnitTestOutput         unitTestOutput     = new UnitTestOutput();

            const int CompletionTimeoutMsec          = 1000;
            DiagnosticPipelineConfiguration settings = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = CompletionTimeoutMsec,
                MaxEventBatchSize             = 1,
                PipelineBufferSize            = 1,
                MaxConcurrency = 1
            };
            UnitTestFilter unitTestFilter = new UnitTestFilter();

            unitTestFilter.EvaluationDelay = TimeSpan.MaxValue;
            Stopwatch stopwatch;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           new IFilter[] { unitTestFilter },
                           new EventSink[] { new EventSink(unitTestOutput, null) },
                           settings))
                {
                    // Saturate the pipeline
                    for (int i = 0; i < 10; i++)
                    {
                        unitTestInput.SendMessage("Hi!");
                    }

                    stopwatch = Stopwatch.StartNew();
                }

            stopwatch.Stop();

            // We should have received no events on the output side--everything should have been stuck in the filter (or dropped because of buffer overflow)
            Assert.Equal(0, unitTestOutput.CallCount);

            // Ensure the pipeline stops within the timeout (plus some padding)
            Assert.InRange(stopwatch.ElapsedMilliseconds, 0, CompletionTimeoutMsec + 200);
        }
Exemple #10
0
        public async Task DoesNotWarnIfNoThrottlingOccurs()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();

            // Need two outputs to force the pipeline to use the BroadcastBlock.
            UnitTestOutput firstOutput  = new UnitTestOutput();
            UnitTestOutput secondOutput = new UnitTestOutput();

            const int InputBufferSize = 100;

            DiagnosticPipelineConfiguration settings = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineBufferSize            = InputBufferSize,
                MaxEventBatchSize             = 4,
                MaxConcurrency                = 2,
                PipelineCompletionTimeoutMsec = 5000
            };

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(firstOutput, null), new EventSink(secondOutput, null) },
                           settings))
                {
                    for (int i = 0; i < InputBufferSize; i++)
                    {
                        unitTestInput.SendMessage(i.ToString());
                    }

                    // There should be no problem for both outputs to receive all events
                    bool eventsReceived = await TaskUtils.PollWaitAsync(() => firstOutput.EventCount == InputBufferSize, TimeSpan.FromMilliseconds(100));

                    Assert.True(eventsReceived);
                    eventsReceived = await TaskUtils.PollWaitAsync(() => secondOutput.EventCount == InputBufferSize, TimeSpan.FromMilliseconds(100));

                    Assert.True(eventsReceived);
                }

            healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Throttling)),
                                      Times.Never());
        }
Exemple #11
0
        public async Task UsableIfExceptionInOutputOccurs()
        {
            Mock <IHealthReporter> healthReporterMock = new Mock <IHealthReporter>();

            UnitTestOutput unitTestOutput = new UnitTestOutput();

            unitTestOutput.FailureCondition = (transmissionSequenceNumber) => transmissionSequenceNumber % 2 == 0;

            DiagnosticPipelineConfiguration settings = new DiagnosticPipelineConfiguration()
            {
                MaxBatchDelayMsec             = 10,
                PipelineCompletionTimeoutMsec = 1000,
                MaxEventBatchSize             = 2
            };
            const int TestEventCount = 32;

            using (UnitTestInput unitTestInput = new UnitTestInput())
                using (DiagnosticPipeline pipeline = new DiagnosticPipeline(
                           healthReporterMock.Object,
                           new IObservable <EventData>[] { unitTestInput },
                           null,
                           new EventSink[] { new EventSink(unitTestOutput, null) },
                           settings))
                {
                    // Half of the events should cause output to fail with an exception
                    for (int i = 0; i < TestEventCount; i++)
                    {
                        unitTestInput.SendMessage("Hi!");
                    }

                    // Wait for the pipeline to drain.
                    await Task.Delay(TimeSpan.FromMilliseconds(300));

                    // We should have at least TestEventCount / MaxEventBatchSize calls to the output
                    int expectedMinCallCount = TestEventCount / settings.MaxEventBatchSize;
                    Assert.InRange(unitTestOutput.CallCount, expectedMinCallCount, TestEventCount);
                    // Half of these calls (modulo 1) should have failed
                    healthReporterMock.Verify(o => o.ReportWarning(It.IsAny <string>(), It.Is <string>(s => s == EventFlowContextIdentifiers.Output)),
                                              Times.Between(unitTestOutput.CallCount / 2, unitTestOutput.CallCount / 2 + 1, Range.Inclusive));
                }
        }
        public static DiagnosticPipeline CreatePipeline(IConfiguration configuration, IHealthReporter healthReporter = null)
        {
            Requires.NotNull(configuration, nameof(configuration));

            if (healthReporter == null)
            {
                healthReporter = CreateHealthReporter(configuration);
            }
            Verify.Operation(healthReporter != null, $"An instance of a health reporter could not be created and none was provider as a parameter to {nameof(CreatePipeline)} method");
            (healthReporter as IRequireActivation)?.Activate();

            IDictionary <string, string> inputFactories;
            IDictionary <string, string> outputFactories;
            IDictionary <string, string> filterFactories;

            CreateItemFactories(configuration, healthReporter, out inputFactories, out outputFactories, out filterFactories);

            // Step 1: instantiate inputs
            IConfigurationSection inputConfigurationSection = configuration.GetSection("inputs");

            if (inputConfigurationSection.GetChildren().Count() == 0)
            {
                ReportSectionEmptyAndThrow(healthReporter, inputConfigurationSection);
            }

            List <ItemWithChildren <IObservable <EventData>, object> > inputCreationResult;

            inputCreationResult = ProcessSection <IObservable <EventData>, object>(
                inputConfigurationSection,
                healthReporter,
                inputFactories,
                childFactories: null,
                childSectionName: null);

            List <IObservable <EventData> > inputs = inputCreationResult.Select(item => item.Item).ToList();

            if (inputs.Count == 0)
            {
                ReportNoItemsCreatedAndThrow(healthReporter, inputConfigurationSection);
            }

            try
            {
                // Step 2: instantiate global filters (if any)
                IConfigurationSection globalFilterConfigurationSection = configuration.GetSection("filters");
                List <ItemWithChildren <IFilter, object> > globalFilterCreationResult;

                // It completely fine to have a pipeline with no globals filters section, or an empty one
                globalFilterCreationResult = ProcessSection <IFilter, object>(
                    globalFilterConfigurationSection,
                    healthReporter,
                    filterFactories,
                    childFactories: null,
                    childSectionName: null);
                List <IFilter> globalFilters = globalFilterCreationResult.Select(item => item.Item).ToList();


                // Step 3: instantiate outputs
                IConfigurationSection outputConfigurationSection = configuration.GetSection("outputs");
                if (outputConfigurationSection.GetChildren().Count() == 0)
                {
                    ReportSectionEmptyAndThrow(healthReporter, outputConfigurationSection);
                }

                List <ItemWithChildren <IOutput, IFilter> > outputCreationResult;
                outputCreationResult = ProcessSection <IOutput, IFilter>(
                    outputConfigurationSection,
                    healthReporter,
                    outputFactories,
                    filterFactories,
                    childSectionName: "filters");

                List <IOutput> outputs = outputCreationResult.Select(item => item.Item).ToList();
                if (outputs.Count == 0)
                {
                    ReportNoItemsCreatedAndThrow(healthReporter, outputConfigurationSection);
                }

                // Step 4: assemble and return the pipeline
                IReadOnlyCollection <EventSink> sinks = outputCreationResult.Select(outputResult =>
                                                                                    new EventSink(outputResult.Item, outputResult.Children)
                                                                                    ).ToList();


                var pipelineSettings = new DiagnosticPipelineConfiguration();
                IConfigurationSection settingsConfigurationSection = configuration.GetSection("settings");
                try
                {
                    if (settingsConfigurationSection.GetChildren().Count() != 0)
                    {
                        settingsConfigurationSection.Bind(pipelineSettings);
                    }
                }
                catch
                {
                    ReportInvalidPipelineConfiguration(healthReporter);
                }

                DiagnosticPipeline pipeline = new DiagnosticPipeline(healthReporter, inputs, globalFilters, sinks, pipelineSettings, disposeDependencies: true);

                // Now the pipeline has assumed ownership of the inputs, setting inputs variable back to null so we won't
                // incorrectly dispose it in the finally block
                inputs = null;
                return(pipeline);
            }
            finally
            {
                DisposeOf(inputs);
            }
        }
        public DiagnosticPipeline(
            IHealthReporter healthReporter,
            IReadOnlyCollection <IObservable <EventData> > inputs,
            IReadOnlyCollection <IFilter> globalFilters,
            IReadOnlyCollection <EventSink> sinks,
            DiagnosticPipelineConfiguration pipelineConfiguration = null,
            bool disposeDependencies    = false,
            TaskScheduler taskScheduler = null)
        {
            Requires.NotNull(healthReporter, nameof(healthReporter));
            Requires.NotNull(inputs, nameof(inputs));
            Requires.Argument(inputs.Count > 0, nameof(inputs), "There must be at least one input");
            Requires.NotNull(sinks, nameof(sinks));
            Requires.Argument(sinks.Count > 0, nameof(sinks), "There must be at least one sink");

            this.batcherTimerDisposalLock = new object();
            this.pipelineConfiguration    = pipelineConfiguration ?? new DiagnosticPipelineConfiguration();
            taskScheduler = taskScheduler ?? TaskScheduler.Current;

            // An estimatie how many batches of events to allow inside the pipeline.
            // We want to be able to process full buffer of events, but also have enough batches in play in case of high concurrency.
            int MaxNumberOfBatchesInProgress = Math.Max(
                5 * this.pipelineConfiguration.MaxConcurrency,
                this.pipelineConfiguration.PipelineBufferSize / this.pipelineConfiguration.MaxEventBatchSize);

            this.Inputs = inputs;
            this.Sinks  = sinks;

            // Just play nice and make sure there is always something to enumerate on
            this.GlobalFilters = globalFilters ?? new IFilter[0];

            this.HealthReporter          = healthReporter;
            this.cancellationTokenSource = new CancellationTokenSource();
            var propagateCompletion = new DataflowLinkOptions()
            {
                PropagateCompletion = true
            };

            // One disposable for each input subscription.
            this.inputSubscriptions = new List <IDisposable>(inputs.Count);

            var inputBuffer = new BufferBlock <EventData>(
                new DataflowBlockOptions()
            {
                BoundedCapacity   = this.pipelineConfiguration.PipelineBufferSize,
                CancellationToken = this.cancellationTokenSource.Token,
                TaskScheduler     = taskScheduler
            });

            this.pipelineHead = inputBuffer;

            var batcher = new BatchBlock <EventData>(
                this.pipelineConfiguration.MaxEventBatchSize,
                new GroupingDataflowBlockOptions()
            {
                BoundedCapacity   = this.pipelineConfiguration.PipelineBufferSize,
                CancellationToken = this.cancellationTokenSource.Token,
                TaskScheduler     = taskScheduler
            }
                );

            inputBuffer.LinkTo(batcher, propagateCompletion);

            ISourceBlock <EventData[]> sinkSource;
            FilterAction filterTransform;

            if (this.GlobalFilters.Count > 0)
            {
                filterTransform = new FilterAction(
                    this.GlobalFilters,
                    this.cancellationTokenSource.Token,
                    MaxNumberOfBatchesInProgress,
                    this.pipelineConfiguration.MaxConcurrency,
                    healthReporter,
                    taskScheduler);
                var globalFiltersBlock = filterTransform.GetFilterBlock();
                batcher.LinkTo(globalFiltersBlock, propagateCompletion);
                sinkSource = globalFiltersBlock;
            }
            else
            {
                sinkSource = batcher;
            }

            bool usingBroadcastBlock = sinks.Count > 1;

            if (usingBroadcastBlock)
            {
                var broadcaster = new BroadcastBlock <EventData[]>(
                    (events) => events?.Select((e) => e.DeepClone()).ToArray(),
                    new DataflowBlockOptions()
                {
                    BoundedCapacity   = MaxNumberOfBatchesInProgress,
                    CancellationToken = this.cancellationTokenSource.Token,
                    TaskScheduler     = taskScheduler
                });
                sinkSource.LinkTo(broadcaster, propagateCompletion);
                sinkSource = broadcaster;
            }

            this.outputCompletionTasks = new List <Task>(sinks.Count);
            foreach (var sink in sinks)
            {
                ISourceBlock <EventData[]> outputSource = sinkSource;

                if (sink.Filters != null && sink.Filters.Count > 0)
                {
                    filterTransform = new FilterAction(
                        sink.Filters,
                        this.cancellationTokenSource.Token,
                        MaxNumberOfBatchesInProgress,
                        this.pipelineConfiguration.MaxConcurrency,
                        healthReporter,
                        taskScheduler);
                    var filterBlock = filterTransform.GetFilterBlock();

                    if (usingBroadcastBlock)
                    {
                        var lossReportingPropagator = new LossReportingPropagatorBlock <EventData[]>(this.HealthReporter);
                        sinkSource.LinkTo(lossReportingPropagator, propagateCompletion);
                        lossReportingPropagator.LinkTo(filterBlock, propagateCompletion);
                    }
                    else
                    {
                        sinkSource.LinkTo(filterBlock, propagateCompletion);
                    }
                    outputSource = filterBlock;
                }
                else if (usingBroadcastBlock)
                {
                    var lossReportingPropagator = new LossReportingPropagatorBlock <EventData[]>(this.HealthReporter);
                    sinkSource.LinkTo(lossReportingPropagator, propagateCompletion);
                    outputSource = lossReportingPropagator;
                }

                OutputAction outputAction = new OutputAction(
                    sink.Output,
                    this.cancellationTokenSource.Token,
                    MaxNumberOfBatchesInProgress,
                    this.pipelineConfiguration.MaxConcurrency,
                    healthReporter,
                    taskScheduler);
                var outputBlock = outputAction.GetOutputBlock();
                outputSource.LinkTo(outputBlock, propagateCompletion);
                this.outputCompletionTasks.Add(outputBlock.Completion);
            }

            IObserver <EventData> inputBufferObserver = new TargetBlockObserver <EventData>(inputBuffer, this.HealthReporter);

            foreach (var input in inputs)
            {
                this.inputSubscriptions.Add(input.Subscribe(inputBufferObserver));
            }

            this.disposed            = false;
            this.disposeDependencies = disposeDependencies;

            this.batcherTimer = new Timer(
                (_) => {
                try
                {
                    lock (this.batcherTimerDisposalLock)
                    {
                        if (!this.disposed)
                        {
                            batcher.TriggerBatch();

                            this.batcherTimer.Change(dueTime: TimeSpan.FromMilliseconds(this.pipelineConfiguration.MaxBatchDelayMsec), period: Timeout.InfiniteTimeSpan);
                        }
                    }
                }
                catch { }
            },
                state: null,
                dueTime: TimeSpan.FromMilliseconds(this.pipelineConfiguration.MaxBatchDelayMsec),
                period: Timeout.InfiniteTimeSpan);
        }
        static void Main(string[] args)
        {
            var healthReporter = new BenchmarkHealthReporter();

            var esConfiguration = new EventSourceConfiguration();

            esConfiguration.ProviderName = "Microsoft-AzureTools-BenchmarkEventSource";
            var eventSourceInput = new EventSourceInput(new EventSourceConfiguration[] { esConfiguration }, healthReporter);

            var metadata = new EventMetadata("importantEvent");

            metadata.Properties.Add("Importance", "High");
            EventMetadataFilter metadataFilter = new EventMetadataFilter(metadata);

            metadataFilter.IncludeCondition = "name==ImportantEvent";

            var oddSequenceNumberFilter  = new CallbackFilter((e) => HasLongPropertyWhere(e.Payload, "sequenceNumber", (n) => (n & 0x1) != 0));
            var evenSequenceNumberFilter = new CallbackFilter((e) => HasLongPropertyWhere(e.Payload, "sequenceNumber", (n) => (n & 0x1) == 0));
            var oddSequenceNumberOutput  = new BenchmarkOutput("Odd sequence number output");
            var evenSequenceNumberOutput = new BenchmarkOutput("Even sequence number output");
            var sinks = new EventSink[]
            {
                new EventSink(oddSequenceNumberOutput, new IFilter[] { oddSequenceNumberFilter }),
                new EventSink(evenSequenceNumberOutput, new IFilter[] { evenSequenceNumberFilter })
            };

            var configuration = new DiagnosticPipelineConfiguration();

            configuration.PipelineCompletionTimeoutMsec = Convert.ToInt32(CoolDownTime.TotalMilliseconds);
            var pipeline = new DiagnosticPipeline(
                healthReporter,
                new IObservable <EventData>[] { eventSourceInput },
                new IFilter[] { metadataFilter },
                sinks,
                configuration);

            Console.WriteLine(string.Format("Starting test... will take {0} seconds", (WarmUpTime + MeasurementTime).TotalSeconds));
            Console.WriteLine("A dot represents 10 000 events submitted");

            DateTimeOffset startTime            = DateTimeOffset.Now;
            DateTimeOffset measurementStartTime = startTime + WarmUpTime;
            DateTimeOffset measurementStopTime  = measurementStartTime + MeasurementTime;
            DateTimeOffset stopTime             = measurementStopTime + CoolDownTime;

            bool debugMode = args.Length == 1 && string.Equals(args[0], "debug", StringComparison.OrdinalIgnoreCase);

            long eventSequenceNo    = 1;
            bool measuring          = debugMode;
            long measuredEventCount = 0;

            while (true)
            {
                // Every tenth event is important
                string name = "RegularEvent";
                if (eventSequenceNo % 10 == 0)
                {
                    name = "ImportantEvent";
                }

                BenchmarkEventSource.Log.ComplexMessage(
                    Guid.NewGuid(),
                    Guid.NewGuid(),
                    eventSequenceNo++,
                    name,
                    DateTime.Now,
                    DateTime.UtcNow,
                    "Complex event message. blah blah");

                if (measuring)
                {
                    measuredEventCount++;
                }

                if (debugMode)
                {
                    if (measuredEventCount == 10)
                    {
                        break;
                    }
                }
                else
                {
                    if (eventSequenceNo % EventBatchSize == 0)
                    {
                        DateTimeOffset now = DateTimeOffset.Now;

                        // In this benchmmark we do not really have a cooldown period, so we do not have to account for not-measuring period at the end.
                        if (!measuring)
                        {
                            bool shouldBeMeasuring = (now >= measurementStartTime && now < measurementStopTime);
                            if (shouldBeMeasuring)
                            {
                                oddSequenceNumberOutput.ResetCounter();
                                evenSequenceNumberOutput.ResetCounter();
                                healthReporter.ResetCounters();
                                measuring = true;
                            }
                        }


                        if (eventSequenceNo % 10000 == 0)
                        {
                            Console.Write(".");
                        }

                        if (now >= stopTime)
                        {
                            break;
                        }
                    }
                }
            }

            Console.WriteLine(string.Empty);
            Console.WriteLine("Enterning cooldown period...");
            Thread.Sleep(CoolDownTime);
            pipeline.Dispose();

            Console.WriteLine(string.Empty);
            Console.WriteLine($"Total events raised during measurement period: {measuredEventCount}");
            Console.WriteLine(oddSequenceNumberOutput.Summary);
            Console.WriteLine(evenSequenceNumberOutput.Summary);
            double processingRate = (oddSequenceNumberOutput.EventCount + evenSequenceNumberOutput.EventCount) / MeasurementTime.TotalSeconds;

            Console.WriteLine($"Processing rate (events/sec): {processingRate}");
            Console.WriteLine(healthReporter.Summary);

            Console.WriteLine("Press any key to exit");
            Console.ReadKey(intercept: true);
        }
        public DiagnosticPipeline(
            IHealthReporter healthReporter,
            IReadOnlyCollection <IObservable <EventData> > inputs,
            IReadOnlyCollection <IFilter> globalFilters,
            IReadOnlyCollection <EventSink> sinks,
            DiagnosticPipelineConfiguration pipelineConfiguration = null,
            bool disposeDependencies = false)
        {
            Requires.NotNull(healthReporter, nameof(healthReporter));
            Requires.NotNull(inputs, nameof(inputs));
            Requires.Argument(inputs.Count > 0, nameof(inputs), "There must be at least one input");
            Requires.NotNull(sinks, nameof(sinks));
            Requires.Argument(sinks.Count > 0, nameof(sinks), "There must be at least one sink");

            this.eventsInProgress = 0;

            this.pipelineConfiguration = pipelineConfiguration ?? new DiagnosticPipelineConfiguration();

            // An estimatie how many batches of events to allow inside the pipeline.
            // We want to be able to process full buffer of events, but also have enough batches in play in case of high concurrency.
            int MaxNumberOfBatchesInProgress = Math.Max(
                5 * this.pipelineConfiguration.MaxConcurrency,
                this.pipelineConfiguration.PipelineBufferSize / this.pipelineConfiguration.MaxEventBatchSize);

            this.Inputs = inputs;
            this.Sinks  = sinks;

            // Just play nice and make sure there is always something to enumerate on
            this.GlobalFilters = globalFilters ?? new IFilter[0];

            this.HealthReporter          = healthReporter;
            this.cancellationTokenSource = new CancellationTokenSource();
            var propagateCompletion = new DataflowLinkOptions()
            {
                PropagateCompletion = true
            };

            this.pipelineLinkDisposables = new List <IDisposable>();
            this.pipelineCompletionTasks = new List <Task>();

            var inputBuffer = new BufferBlock <EventData>(
                new DataflowBlockOptions()
            {
                BoundedCapacity   = this.pipelineConfiguration.PipelineBufferSize,
                CancellationToken = this.cancellationTokenSource.Token
            });

            this.pipelineHead = inputBuffer;
            this.pipelineCompletionTasks.Add(inputBuffer.Completion);

            var batcher = new BatchBlock <EventData>(
                this.pipelineConfiguration.MaxEventBatchSize,
                new GroupingDataflowBlockOptions()
            {
                BoundedCapacity   = this.pipelineConfiguration.MaxEventBatchSize,
                CancellationToken = this.cancellationTokenSource.Token
            }
                );

            this.pipelineLinkDisposables.Add(inputBuffer.LinkTo(batcher, propagateCompletion));
            this.pipelineCompletionTasks.Add(batcher.Completion);

            this.pipelineLinkDisposables.Add(new Timer(
                                                 (unused) => batcher.TriggerBatch(),
                                                 state: null,
                                                 dueTime: TimeSpan.FromMilliseconds(this.pipelineConfiguration.MaxBatchDelayMsec),
                                                 period: TimeSpan.FromMilliseconds(this.pipelineConfiguration.MaxBatchDelayMsec)));

            ISourceBlock <EventData[]> sinkSource;
            FilterAction filterTransform;

            if (this.GlobalFilters.Count > 0)
            {
                filterTransform = new FilterAction(
                    this.GlobalFilters,
                    this.cancellationTokenSource.Token,
                    MaxNumberOfBatchesInProgress,
                    this.pipelineConfiguration.MaxConcurrency,
                    healthReporter,
                    this.OnEventsFilteredOut);
                var globalFiltersBlock = filterTransform.GetFilterBlock();
                this.pipelineLinkDisposables.Add(batcher.LinkTo(globalFiltersBlock, propagateCompletion));
                this.pipelineCompletionTasks.Add(globalFiltersBlock.Completion);
                sinkSource = globalFiltersBlock;
            }
            else
            {
                sinkSource = batcher;
            }

            if (sinks.Count > 1)
            {
                // After broadcasting we will effectively have (sinks.Count - 1) * batch.Length more events in the pipeline,
                // because the broadcaster is cloning the events for the sake of each sink (filters-output combination).
                var eventCounter = new TransformBlock <EventData[], EventData[]>(
                    (batch) => { Interlocked.Add(ref this.eventsInProgress, (sinks.Count - 1) * batch.Length);  return(batch); },
                    new ExecutionDataflowBlockOptions()
                {
                    BoundedCapacity   = MaxNumberOfBatchesInProgress,
                    CancellationToken = this.cancellationTokenSource.Token
                });
                this.pipelineLinkDisposables.Add(sinkSource.LinkTo(eventCounter, propagateCompletion));
                this.pipelineCompletionTasks.Add(eventCounter.Completion);

                var broadcaster = new BroadcastBlock <EventData[]>(
                    (events) => events?.Select((e) => e.DeepClone()).ToArray(),
                    new DataflowBlockOptions()
                {
                    BoundedCapacity   = MaxNumberOfBatchesInProgress,
                    CancellationToken = this.cancellationTokenSource.Token
                });
                this.pipelineLinkDisposables.Add(eventCounter.LinkTo(broadcaster, propagateCompletion));
                this.pipelineCompletionTasks.Add(broadcaster.Completion);
                sinkSource = broadcaster;
            }

            foreach (var sink in sinks)
            {
                ISourceBlock <EventData[]> outputSource = sinkSource;
                if (sink.Filters != null && sink.Filters.Count > 0)
                {
                    filterTransform = new FilterAction(
                        sink.Filters,
                        this.cancellationTokenSource.Token,
                        MaxNumberOfBatchesInProgress,
                        this.pipelineConfiguration.MaxConcurrency,
                        healthReporter,
                        this.OnEventsFilteredOut);
                    var filterBlock = filterTransform.GetFilterBlock();
                    this.pipelineLinkDisposables.Add(sinkSource.LinkTo(filterBlock, propagateCompletion));
                    this.pipelineCompletionTasks.Add(filterBlock.Completion);
                    outputSource = filterBlock;
                }

                OutputAction outputAction = new OutputAction(
                    sink.Output,
                    this.cancellationTokenSource.Token,
                    MaxNumberOfBatchesInProgress,
                    this.pipelineConfiguration.MaxConcurrency,
                    healthReporter,
                    (eventsSentCount) => Interlocked.Add(ref this.eventsInProgress, -eventsSentCount));
                var outputBlock = outputAction.GetOutputBlock();
                this.pipelineLinkDisposables.Add(outputSource.LinkTo(outputBlock, propagateCompletion));
                this.pipelineCompletionTasks.Add(outputBlock.Completion);
            }

            IObserver <EventData> inputBufferObserver = new TargetBlockObserver <EventData>(
                inputBuffer,
                this.HealthReporter,
                () => Interlocked.Increment(ref this.eventsInProgress));

            this.inputSubscriptions = new List <IDisposable>(inputs.Count);
            foreach (var input in inputs)
            {
                this.inputSubscriptions.Add(input.Subscribe(inputBufferObserver));
            }

            this.disposed            = false;
            this.disposeDependencies = disposeDependencies;
        }
Exemple #16
0
        public DiagnosticPipeline(
            IHealthReporter healthReporter,
            IReadOnlyCollection <IObservable <EventData> > inputs,
            IReadOnlyCollection <IFilter> globalFilters,
            IReadOnlyCollection <EventSink> sinks,
            DiagnosticPipelineConfiguration pipelineConfiguration = null,
            bool disposeDependencies = false)
        {
            Requires.NotNull(healthReporter, nameof(healthReporter));
            Requires.NotNull(inputs, nameof(inputs));
            Requires.Argument(inputs.Count > 0, nameof(inputs), "There must be at least one input");
            Requires.NotNull(sinks, nameof(sinks));
            Requires.Argument(sinks.Count > 0, nameof(sinks), "There must be at least one sink");

            this.pipelineConfiguration = pipelineConfiguration ?? new DiagnosticPipelineConfiguration();

            this.Inputs = inputs;
            this.Sinks  = sinks;
            // Just play nice and make sure there is always something to enumerate on
            this.GlobalFilters           = globalFilters ?? new IFilter[0];
            this.HealthReporter          = healthReporter;
            this.cancellationTokenSource = new CancellationTokenSource();
            var propagateCompletion = new DataflowLinkOptions()
            {
                PropagateCompletion = true
            };

            this.pipelineDisposables = new List <IDisposable>();

            var inputBuffer = new BufferBlock <EventData>(
                new DataflowBlockOptions()
            {
                BoundedCapacity   = this.pipelineConfiguration.PipelineBufferSize,
                CancellationToken = this.cancellationTokenSource.Token
            });

            this.pipelineHead = inputBuffer;

            var batcher = new BatchBlock <EventData>(
                this.pipelineConfiguration.MaxEventBatchSize,
                new GroupingDataflowBlockOptions()
            {
                BoundedCapacity   = this.pipelineConfiguration.MaxEventBatchSize,
                CancellationToken = this.cancellationTokenSource.Token
            }
                );

            this.pipelineDisposables.Add(inputBuffer.LinkTo(batcher, propagateCompletion));

            this.pipelineDisposables.Add(new Timer(
                                             (unused) => batcher.TriggerBatch(),
                                             state: null,
                                             dueTime: TimeSpan.FromMilliseconds(this.pipelineConfiguration.MaxBatchDelayMsec),
                                             period: TimeSpan.FromMilliseconds(this.pipelineConfiguration.MaxBatchDelayMsec)));

            ISourceBlock <EventData[]> sinkSource;
            FilterAction filterTransform;

            if (this.GlobalFilters.Count > 0)
            {
                filterTransform = new FilterAction(
                    this.GlobalFilters,
                    this.cancellationTokenSource.Token,
                    this.pipelineConfiguration.MaxEventBatchSize,
                    this.pipelineConfiguration.MaxConcurrency,
                    healthReporter);
                var globalFiltersBlock = filterTransform.GetFilterBlock();
                this.pipelineDisposables.Add(batcher.LinkTo(globalFiltersBlock, propagateCompletion));
                sinkSource = globalFiltersBlock;
            }
            else
            {
                sinkSource = batcher;
            }

            if (sinks.Count > 1)
            {
                var broadcaster = new BroadcastBlock <EventData[]>(
                    (events) => events?.Select((e) => e.DeepClone()).ToArray(),
                    new DataflowBlockOptions()
                {
                    BoundedCapacity   = this.pipelineConfiguration.MaxEventBatchSize,
                    CancellationToken = this.cancellationTokenSource.Token
                });
                this.pipelineDisposables.Add(sinkSource.LinkTo(broadcaster, propagateCompletion));
                sinkSource = broadcaster;
            }

            foreach (var sink in sinks)
            {
                ISourceBlock <EventData[]> outputSource = sinkSource;
                if (sink.Filters != null && sink.Filters.Count > 0)
                {
                    filterTransform = new FilterAction(
                        sink.Filters,
                        this.cancellationTokenSource.Token,
                        this.pipelineConfiguration.MaxEventBatchSize,
                        this.pipelineConfiguration.MaxConcurrency,
                        healthReporter);
                    var filterBlock = filterTransform.GetFilterBlock();
                    this.pipelineDisposables.Add(sinkSource.LinkTo(filterBlock, propagateCompletion));
                    outputSource = filterBlock;
                }

                OutputAction outputAction = new OutputAction(
                    sink.Output,
                    this.cancellationTokenSource.Token,
                    this.pipelineConfiguration.MaxEventBatchSize,
                    this.pipelineConfiguration.MaxConcurrency,
                    healthReporter);
                ActionBlock <EventData[]> outputBlock = outputAction.GetOutputBlock();
                this.pipelineDisposables.Add(outputSource.LinkTo(outputBlock, propagateCompletion));
            }

            IObserver <EventData> inputBufferObserver = new TargetBlockObserver <EventData>(inputBuffer, this.HealthReporter);

            this.inputSubscriptions = new List <IDisposable>(inputs.Count);
            foreach (var input in inputs)
            {
                this.inputSubscriptions.Add(input.Subscribe(inputBufferObserver));
            }

            this.disposed            = false;
            this.disposeDependencies = disposeDependencies;
        }