Пример #1
0
        public async Task ProducerInitializesPropertiesWhenPublishing()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(1))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new IdempotentProducerOptions {
                    EnableIdempotentPartitions = true
                };

                await using var producer = new IdempotentProducer(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition   = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).First();
                var sendOptions = new SendEventOptions {
                    PartitionId = partition
                };

                var events = EventGenerator.CreateEvents(10).ToArray();
                await producer.SendAsync(events, sendOptions, cancellationSource.Token);

                var partitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition);

                Assert.That(partitionProperties, Is.Not.Null, "The properties should have been created.");
                Assert.That(partitionProperties.IsIdempotentPublishingEnabled, Is.True, "Idempotent publishing should be enabled.");
                Assert.That(partitionProperties.ProducerGroupId.HasValue, Is.True, "The producer group identifier should have a value.");
                Assert.That(partitionProperties.OwnerLevel.HasValue, Is.True, "The owner level should have a value.");
                Assert.That(partitionProperties.LastPublishedSequenceNumber.HasValue, Is.True, "The last published sequence number should have a value.");
            }
        }
Пример #2
0
        public async Task ProducerCanPublishEvents(EventHubsTransportType transportType)
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(1))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new IdempotentProducerOptions {
                    EnableIdempotentPartitions = true, ConnectionOptions = new EventHubConnectionOptions {
                        TransportType = transportType
                    }
                };

                await using var producer = new IdempotentProducer(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition   = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).First();
                var sendOptions = new SendEventOptions {
                    PartitionId = partition
                };

                Assert.That(async() => await producer.SendAsync(EventGenerator.CreateEvents(2), sendOptions, cancellationSource.Token), Throws.Nothing, "The first publishing operation was not successful.");
                Assert.That(async() => await producer.SendAsync(EventGenerator.CreateEvents(2), sendOptions, cancellationSource.Token), Throws.Nothing, "The second publishing operation was not successful.");
            }
        }
Пример #3
0
        private async Task PerformEnqueue(EventHubBufferedProducerClient producer,
                                          CancellationToken cancellationToken)
        {
            var events = EventGenerator.CreateEvents(_testConfiguration.MaximumEventListSize,
                                                     _testConfiguration.EventEnqueueListSize,
                                                     _testConfiguration.LargeMessageRandomFactorPercent,
                                                     _testConfiguration.PublishingBodyMinBytes,
                                                     _testConfiguration.PublishingBodyRegularMaxBytes);

            try
            {
                await producer.EnqueueEventsAsync(events, cancellationToken).ConfigureAwait(false);

                _metrics.Client.GetMetric(_metrics.EventsEnqueued).TrackValue(_testConfiguration.EventEnqueueListSize);
            }
            catch (TaskCanceledException)
            {
                // Run is completed.
            }
            catch (Exception ex)
            {
                var eventProperties = new Dictionary <String, String>();

                // Track that the exception took place during the enqueuing of an event

                eventProperties.Add("Process", "Enqueue");

                _metrics.Client.TrackException(ex, eventProperties);
            }
        }
        public async Task ProducerManagesConcurrencyWhenPublishingEvents()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(1))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new EventHubProducerClientOptions {
                    EnableIdempotentPartitions = true
                };

                await using var producer = new EventHubProducerClient(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition   = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).First();
                var sendOptions = new SendEventOptions {
                    PartitionId = partition
                };

                async Task sendEvents(int delayMilliseconds)
                {
                    await Task.Delay(delayMilliseconds);

                    await producer.SendAsync(EventGenerator.CreateEvents(2), sendOptions, cancellationSource.Token);
                }

                var pendingSends = Task.WhenAll(
                    sendEvents(100),
                    sendEvents(50),
                    sendEvents(0)
                    );

                Assert.That(async() => await pendingSends, Throws.Nothing);
            }
        }
        public async Task ProducerUpdatesPropertiesAfterPublishingEvents()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(1))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new EventHubProducerClientOptions {
                    EnableIdempotentPartitions = true
                };

                await using var producer = new EventHubProducerClient(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).First();
                var initialPartitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition);

                var sendOptions = new SendEventOptions {
                    PartitionId = partition
                };
                var events = EventGenerator.CreateEvents(10).ToArray();
                await producer.SendAsync(events, sendOptions, cancellationSource.Token);

                var updatedPartitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition);

                Assert.That(updatedPartitionProperties.IsIdempotentPublishingEnabled, Is.True, "Idempotent publishing should be enabled.");
                Assert.That(updatedPartitionProperties.ProducerGroupId, Is.EqualTo(initialPartitionProperties.ProducerGroupId), "The producer group identifier should not have changed.");
                Assert.That(updatedPartitionProperties.OwnerLevel, Is.EqualTo(initialPartitionProperties.OwnerLevel), "The owner level should not have changed.");
                Assert.That(updatedPartitionProperties.LastPublishedSequenceNumber, Is.GreaterThan(initialPartitionProperties.LastPublishedSequenceNumber), "The last published sequence number should have increased.");
            }
        }
        public async Task ProducerAllowsPublishingConcurrentlyToDifferentPartitions()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(4))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new EventHubProducerClientOptions {
                    EnableIdempotentPartitions = true
                };

                await using var producer = new EventHubProducerClient(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                async Task sendEvents(string partition, int delayMilliseconds)
                {
                    await Task.Delay(delayMilliseconds);

                    await producer.SendAsync(EventGenerator.CreateEvents(5), new SendEventOptions { PartitionId = partition }, cancellationSource.Token);
                }

                var partitions = await producer.GetPartitionIdsAsync(cancellationSource.Token);

                var pendingSends = new List <Task>();

                foreach (var partition in partitions)
                {
                    pendingSends.Add(sendEvents(partition, 50));
                    pendingSends.Add(sendEvents(partition, 0));
                }

                Assert.That(async() => await Task.WhenAll(pendingSends), Throws.Nothing);
            }
        }
        /// <summary>
        /// CollectData turn on logging of data from 'eventSourceName' to the file 'dataFileName'.
        /// It will then call EventGenerator.CreateEvents and wait 12 seconds for it to generate some data.
        /// </summary>
        static void CollectData(string eventSourceName, string dataFileName)
        {
            // Today you have to be Admin to turn on ETW events (anyone can write ETW events).
            if (!(TraceEventSession.IsElevated() ?? false))
            {
                Out.WriteLine("To turn on ETW events you need to be Administrator, please run from an Admin process.");
                Debugger.Break();
                return;
            }

            // As mentioned below, sessions can outlive the process that created them.  Thus you need a way of
            // naming the session so that you can 'reconnect' to it from another process.   This is what the name
            // is for.  It can be anything, but it should be descriptive and unique.   If you expect multiple versions
            // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix)
            // however this is dangerous because you can leave data collection on if the program ends unexpectedly.
            //
            // In this case we tell the session to place the data in MonitorToFileData.etl.
            var sessionName = "SimpleMontitorSession";

            Out.WriteLine("Creating a '{0}' session writing to {1}", sessionName, dataFileName);
            Out.WriteLine("Use 'logman query -ets' to see active sessions.");
            Out.WriteLine("Use 'logman stop {0} -ets' to manually stop orphans.", sessionName);
            using (var session = new TraceEventSession(sessionName, dataFileName))      // Since we give it a file name, the data goes there.
            {
                /* BY DEFAULT ETW SESSIONS SURVIVE THE DEATH OF THE PROESS THAT CREATES THEM! */
                // Unlike most other resources on the system, ETW session live beyond the lifetime of the
                // process that created them.   This is very useful in some scenarios, but also creates the
                // very real possibility of leaving 'orphan' sessions running.
                //
                // To help avoid this by default TraceEventSession sets 'StopOnDispose' so that it will stop
                // the ETW session if the TraceEventSession dies.   Thus executions that 'clean up' the TraceEventSession
                // will clean up the ETW session.   This covers many cases (including throwing exceptions)
                //
                // However if the process is killed manually (including control C) this cleanup will not happen.
                // Thus best practices include
                //
                //     * Add a Control C handler that calls session.Dispose() so it gets cleaned up in this common case
                //     * use the same session name run-to-run so you don't create many orphans.
                //
                // By default TraceEventSessions are in 'create' mode where it assumes you want to create a new session.
                // In this mode if a session already exists, it is stopped and the new one is created.
                //
                // Here we install the Control C handler.   It is OK if Dispose is called more than once.
                Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { session.Dispose(); };

                // Enable my provider, you can call many of these on the same session to get other events.
                var restarted = session.EnableProvider(MyEventSource.Log.Name);
                if (restarted)      // Generally you don't bother with this warning, but for the demo we do.
                {
                    Out.WriteLine("The session {0} was already active, it has been restarted.", sessionName);
                }

                // Start another thread that Causes MyEventSource to create some events
                // Normally this code as well as the EventSource itself would be in a different process.
                EventGenerator.CreateEvents();

                Out.WriteLine("Waiting 12 seconds for events to come in.");
                Thread.Sleep(12000);
            }
        }
        /// <summary>
        ///   Performs the tasks needed to initialize and set up the environment for the test scenario.
        ///   This setup will take place once for each instance, running after the global setup has
        ///   completed.
        /// </summary>
        ///
        public override async Task SetupAsync()
        {
            await base.SetupAsync();

            Scope = await EventHubScope.CreateAsync(Options.PartitionCount).ConfigureAwait(false);

            var producerOptions = new EventHubBufferedProducerClientOptions
            {
                MaximumWaitTime = (Options.MaximumWaitTimeMilliseconds.HasValue) ? TimeSpan.FromMilliseconds(Options.MaximumWaitTimeMilliseconds.Value) : null,
                MaximumEventBufferLengthPerPartition = Options.MaximumBufferLength,
                MaximumConcurrentSendsPerPartition   = Options.MaximumConcurrentSendsPerPartition
            };

            if (Options.MaximumConcurrentSends.HasValue)
            {
                producerOptions.MaximumConcurrentSends = Options.MaximumConcurrentSends.Value;
            }

            _producer = new EventHubBufferedProducerClient(TestEnvironment.EventHubsConnectionString, Scope.EventHubName, producerOptions);

            // Create the handlers that call into the performance test infrastructure to
            // report when errors and events are observed.

            _producer.SendEventBatchFailedAsync += args =>
            {
                // Do not flag cancellation as a failure; it is expected for in-flight batches when
                // the test run is cleaning up.

                if (!typeof(OperationCanceledException).IsAssignableFrom(args.Exception.GetType()))
                {
                    ErrorRaised(args.Exception);
                }

                return(Task.CompletedTask);
            };

            _producer.SendEventBatchSucceededAsync += args =>
            {
                for (var index = 0; index < args.EventBatch.Count; ++index)
                {
                    EventRaised();
                }

                return(Task.CompletedTask);
            };

            // Read the available partitions and buffer a single events to establish the
            // connection and link and start reading the buffers.

            Partitions = await _producer.GetPartitionIdsAsync().ConfigureAwait(false);

            await _producer.EnqueueEventsAsync(EventGenerator.CreateEvents(1)).ConfigureAwait(false);

            // Start the background publishing task.

            _backgroundCancellationSource = new CancellationTokenSource();
            _backgroundBufferingTask      = EnqueueEvents(_backgroundCancellationSource.Token);
        }
Пример #9
0
        public async Task ProducerCanInitializeWithPartialPartitionOptions()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(2))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new IdempotentProducerOptions {
                    EnableIdempotentPartitions = true
                };

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition           = default(string);
                var partitionProperties = default(PartitionPublishingProperties);

                // Create a producer for a small scope that will Send some events and read the properties.

                await using (var initialProducer = new IdempotentProducer(connectionString, options))
                {
                    partition = (await initialProducer.GetPartitionIdsAsync(cancellationSource.Token)).Last();

                    await initialProducer.SendAsync(EventGenerator.CreateEvents(10), new SendEventOptions { PartitionId = partition }, cancellationSource.Token);

                    partitionProperties = await initialProducer.GetPartitionPublishingPropertiesAsync(partition);
                }

                // Create a new producer using the previously read properties to set options for the partition.

                options.PartitionOptions.Add(partition, new PartitionPublishingOptions
                {
                    ProducerGroupId = partitionProperties.ProducerGroupId,
                    OwnerLevel      = partitionProperties.OwnerLevel
                });

                Assert.That(options.PartitionOptions[partition].StartingSequenceNumber.HasValue, Is.False, "The partition options should not specifiy a starting sequence number.");
                await using var producer = new IdempotentProducer(connectionString, options);

                // Verify that the properties were fully initialized when using partial options.

                partitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition);

                Assert.That(partitionProperties, Is.Not.Null, "The properties should have been created.");
                Assert.That(partitionProperties.IsIdempotentPublishingEnabled, Is.True, "Idempotent publishing should be enabled.");
                Assert.That(partitionProperties.ProducerGroupId.HasValue, Is.True, "The producer group identifier should have a value.");
                Assert.That(partitionProperties.OwnerLevel.HasValue, Is.True, "The owner level should have a value.");
                Assert.That(partitionProperties.LastPublishedSequenceNumber.HasValue, Is.True, "The last published sequence number should have a value.");

                // Ensure that the state supports publishing.

                Assert.That(async() => await producer.SendAsync(EventGenerator.CreateEvents(10), new SendEventOptions {
                    PartitionId = partition
                }, cancellationSource.Token), Throws.Nothing);
            }
        }
Пример #10
0
    /// <summary>
    ///   Performs the actual sends to the Event Hub for this particular test, using the <see cref="EventHubProducerClient" /> parameter.
    ///   This method generates events using the <see cref="PublisherConfiguration" />  configurations.
    /// </summary>
    ///
    /// <param name="producer">The <see cref="EventHubProducerClient" /> to send events to.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToke"/> instance to signal the request to cancel the operation.</param>
    ///
    private async Task PerformSend(EventHubProducerClient producer,
                                   CancellationToken cancellationToken)
    {
        // Create the batch and generate a set of random events, keeping only those that were able to fit into the batch.
        // Because there is a side-effect of TryAdd in the statement, ensure that ToList is called to materialize the set
        // or the batch will be empty at send.

        using var batch = await producer.CreateBatchAsync().ConfigureAwait(false);

        var events = EventGenerator.CreateEvents(batch.MaximumSizeInBytes,
                                                 _publisherconfiguration.PublishBatchSize,
                                                 _publisherconfiguration.LargeMessageRandomFactorPercent,
                                                 _publisherconfiguration.PublishingBodyMinBytes,
                                                 _publisherconfiguration.PublishingBodyRegularMaxBytes);

        foreach (var currentEvent in events)
        {
            if (!batch.TryAdd(currentEvent))
            {
                break;
            }
        }

        // Publish the events and report them, capturing any failures specific to the send operation.

        try
        {
            if (batch.Count > 0)
            {
                _metrics.Client.GetMetric(Metrics.PublishAttempts, Metrics.TestName).TrackValue(1, _testName);
                await producer.SendAsync(batch, cancellationToken).ConfigureAwait(false);
            }

            _metrics.Client.GetMetric(Metrics.EventsPublished, Metrics.TestName).TrackValue(batch.Count, _testName);
            _metrics.Client.GetMetric(Metrics.BatchesPublished, Metrics.TestName).TrackValue(1, _testName);
            _metrics.Client.GetMetric(Metrics.TotalPublishedSizeBytes, Metrics.TestName).TrackValue(batch.SizeInBytes, _testName);
        }
        catch (TaskCanceledException)
        {
            _metrics.Client.GetMetric(Metrics.PublishAttempts, Metrics.TestName).TrackValue(-1, _testName);
        }
        catch (Exception ex)
        {
            var exceptionProperties = new Dictionary <String, String>();
            exceptionProperties.Add("Process", "Send");
            exceptionProperties.Add(Metrics.TestName, _testName);

            _metrics.Client.TrackException(ex, exceptionProperties);
        }
    }
        public async Task ProducerSequencesBatches()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(2))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new EventHubProducerClientOptions {
                    EnableIdempotentPartitions = true
                };

                await using var producer = new EventHubProducerClient(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition    = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).Last();
                var batchOptions = new CreateBatchOptions {
                    PartitionId = partition
                };

                var partitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition);

                var eventSequenceNumber = partitionProperties.LastPublishedSequenceNumber;

                using var firstBatch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token);

                firstBatch.TryAdd(EventGenerator.CreateEvents(1).First());
                firstBatch.TryAdd(EventGenerator.CreateEvents(1).First());
                firstBatch.TryAdd(EventGenerator.CreateEvents(1).First());

                using var secondBatch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token);

                secondBatch.TryAdd(EventGenerator.CreateEvents(1).First());
                secondBatch.TryAdd(EventGenerator.CreateEvents(1).First());
                secondBatch.TryAdd(EventGenerator.CreateEvents(1).First());
                secondBatch.TryAdd(EventGenerator.CreateEvents(1).First());

                Assert.That(firstBatch.StartingPublishedSequenceNumber.HasValue, Is.False, "Batches should start out as unpublished with no sequence number, the first batch was incorrect.");
                Assert.That(secondBatch.StartingPublishedSequenceNumber.HasValue, Is.False, "Batches should start out as unpublished with no sequence number, the second batch was incorrect.");

                await producer.SendAsync(firstBatch, cancellationSource.Token);

                await producer.SendAsync(secondBatch, cancellationSource.Token);

                Assert.That(firstBatch.StartingPublishedSequenceNumber.HasValue, "Batches should be sequenced after publishing, the first batch was incorrect.");
                Assert.That(firstBatch.StartingPublishedSequenceNumber, Is.EqualTo(eventSequenceNumber + 1), "Batches should be sequenced after publishing, the first batch was incorrect.");

                Assert.That(secondBatch.StartingPublishedSequenceNumber.HasValue, "Batches should be sequenced after publishing, the second batch was incorrect.");
                Assert.That(secondBatch.StartingPublishedSequenceNumber, Is.EqualTo(eventSequenceNumber + 1 + firstBatch.Count), "Batches should be sequenced after publishing, the second batch was incorrect.");
            }
        }
Пример #12
0
        public async Task ProducerIsRejectedWithPartitionOptionsForInvalidState()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(2))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new IdempotentProducerOptions {
                    EnableIdempotentPartitions = true
                };

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition           = default(string);
                var partitionProperties = default(PartitionPublishingProperties);

                // Create a producer for a small scope that will Send some events and read the properties.

                await using (var initialProducer = new IdempotentProducer(connectionString, options))
                {
                    partition = (await initialProducer.GetPartitionIdsAsync(cancellationSource.Token)).Last();

                    await initialProducer.SendAsync(EventGenerator.CreateEvents(10), new SendEventOptions { PartitionId = partition }, cancellationSource.Token);

                    partitionProperties = await initialProducer.GetPartitionPublishingPropertiesAsync(partition);
                }

                // Create a new producer using the previously read properties to set options for the partition.

                options.PartitionOptions.Add(partition, new PartitionPublishingOptions
                {
                    ProducerGroupId        = partitionProperties.ProducerGroupId,
                    OwnerLevel             = partitionProperties.OwnerLevel,
                    StartingSequenceNumber = (partitionProperties.LastPublishedSequenceNumber - 5)
                });

                await using var producer = new IdempotentProducer(connectionString, options);

                Assert.That(async() => await producer.SendAsync(EventGenerator.CreateEvents(10), new SendEventOptions {
                    PartitionId = partition
                }, cancellationSource.Token),
                            Throws.InstanceOf <EventHubsException>().And.Property("Reason").EqualTo(EventHubsException.FailureReason.InvalidClientState));
            }
        }
        public async Task ProducerSequencesEvents()
        {
            await using (EventHubScope scope = await EventHubScope.CreateAsync(2))
            {
                var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
                var options          = new EventHubProducerClientOptions {
                    EnableIdempotentPartitions = true
                };

                await using var producer = new EventHubProducerClient(connectionString, options);

                var cancellationSource = new CancellationTokenSource();
                cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

                var partition   = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).Last();
                var sendOptions = new SendEventOptions {
                    PartitionId = partition
                };

                var partitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition);

                var eventSequenceNumber = partitionProperties.LastPublishedSequenceNumber;

                var events = EventGenerator.CreateEvents(10).ToArray();
                Assert.That(events.Any(item => item.PublishedSequenceNumber.HasValue), Is.False, "Events should start out as unpublished with no sequence number.");

                await producer.SendAsync(events, sendOptions, cancellationSource.Token);

                Assert.That(events.All(item => item.PublishedSequenceNumber.HasValue), Is.True, "Events should be sequenced after publishing.");

                foreach (var item in events)
                {
                    Assert.That(item.PublishedSequenceNumber, Is.EqualTo(++eventSequenceNumber), $"The sequence numbers should be contiguous.  Event { eventSequenceNumber } was out of order.");
                }
            }
        }
Пример #14
0
        /// <summary>
        /// CollectData doe will turn on logging of data from 'eventSourceName' to the file 'dataFileName'.
        /// It will then call EventGenerator.CreateEvents and wait 12 seconds for it to generate some data.
        /// </summary>
        static void CollectData(string eventSourceName, string dataFileName)
        {
            // Today you have to be Admin to turn on ETW events (anyone can write ETW events).
            if (!(TraceEventSession.IsElevated() ?? false))
            {
                Out.WriteLine("To turn on ETW events you need to be Administrator, please run from an Admin process.");
                Debugger.Break();
                return;
            }

            // As mentioned below, sessions can outlive the process that created them.  Thus you need a way of
            // naming the session so that you can 'reconnect' to it from another process.   This is what the name
            // is for.  It can be anything, but it should be descriptive and unique.   If you expect multiple versions
            // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix)
            // however this is dangerous because you can leave data collection on if the program ends unexpectedly.
            //
            // In this case we tell the session to place the data in MonitorToFileData.etl.
            var sessionName = "SimpleTraceLogSession";

            Out.WriteLine("Creating a '{0}' session writing to {1}", sessionName, dataFileName);
            Out.WriteLine("Use 'logman query -ets' to see active sessions.");
            Out.WriteLine("Use 'logman stop {0} -ets' to manually stop orphans.", sessionName);
            using (var session = new TraceEventSession(sessionName, dataFileName))      // Since we give it a file name, the data goes there.
                using (var kernelSession = new TraceEventSession(KernelTraceEventParser.KernelSessionName, Path.ChangeExtension(dataFileName, ".kernel.etl")))
                {
                    /* BY DEFAULT ETW SESSIONS SURVIVE THE DEATH OF THE PROESS THAT CREATES THEM! */
                    // Unlike most other resources on the system, ETW session live beyond the lifetime of the
                    // process that created them.   This is very useful in some scenarios, but also creates the
                    // very real possibility of leaving 'orphan' sessions running.
                    //
                    // To help avoid this by default TraceEventSession sets 'StopOnDispose' so that it will stop
                    // the ETW session if the TraceEventSession dies.   Thus executions that 'clean up' the TraceEventSession
                    // will clean up the ETW session.   This covers many cases (including throwing exceptions)
                    //
                    // However if the process is killed manually (including control C) this cleanup will not happen.
                    // Thus best practices include
                    //
                    //     * Add a Control C handler that calls session.Dispose() so it gets cleaned up in this common case
                    //     * use the same session name run-to-run so you don't create many orphans.
                    //
                    // By default TraceEventSessions are in 'create' mode where it assumes you want to create a new session.
                    // In this mode if a session already exists, it is stopped and the new one is created.
                    //
                    // Here we install the Control C handler.   It is OK if Dispose is called more than once.
                    Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { session.Dispose(); kernelSession.Dispose(); };

                    // Enable kernel events.
                    kernelSession.EnableKernelProvider(KernelTraceEventParser.Keywords.ImageLoad | KernelTraceEventParser.Keywords.Process | KernelTraceEventParser.Keywords.Thread);

                    // Enable my provider, you can call many of these on the same session to get events from other providers

                    // Turn on the eventSource given its name.
                    // Note we turn on Verbose level all keywords (ulong.MaxValue == 0xFFF....) and turn on stacks for
                    // this provider (for all events, until Windows 8.1 you can only turn on stacks for every event
                    // for a particular provider or no stacks)
                    var options = new TraceEventProviderOptions()
                    {
                        StacksEnabled = true
                    };
                    var restarted = session.EnableProvider(eventSourceName, TraceEventLevel.Verbose, ulong.MaxValue, options);
                    if (restarted)  // Generally you don't bother with this warning, but for the demo we do.
                    {
                        Out.WriteLine("The session {0} was already active, it has been restarted.", sessionName);
                    }

                    // We also turn on CLR events because we need them to decode Stacks and we also get exception events (and their stacks)
                    session.EnableProvider(ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)ClrTraceEventParser.Keywords.Default);

                    // Start another thread that Causes MyEventSource to create some events
                    // Normally this code as well as the EventSource itself would be in a different process.
                    EventGenerator.CreateEvents();

                    // Also generate some exceptions so we have interesting stacks to look at
                    Thread.Sleep(100);
                    EventGenerator.GenerateExceptions();

                    Out.WriteLine("Waiting 12 seconds for events to come in.");
                    Thread.Sleep(12000);

                    // Because the process in question (this process) lives both before and after the time the events were
                    // collected, we don't have complete information about JIT compiled methods in that method.   There are
                    // some methods that were JIT compiled before the session started (e.g. SimpleTraceLog.Main) for which
                    // we do not have information.   We collect this by forcing a CLR 'rundown' which will dump method information
                    // for JIT compiled methods that were not present.  If you know that the process of interest ends before
                    // data collection ended or that data collection started before the process started, then this is not needed.
                    Out.WriteLine("Forcing rundown of JIT methods.");
                    var rundownFileName = Path.ChangeExtension(dataFileName, ".clrRundown.etl");
                    using (var rundownSession = new TraceEventSession(sessionName + "Rundown", rundownFileName))
                    {
                        rundownSession.EnableProvider(ClrRundownTraceEventParser.ProviderGuid, TraceEventLevel.Verbose, (ulong)ClrRundownTraceEventParser.Keywords.Default);
                        // Poll until 2 second goes by without growth.
                        for (var prevLength = new FileInfo(rundownFileName).Length; ;)
                        {
                            Thread.Sleep(2000);
                            var newLength = new FileInfo(rundownFileName).Length;
                            if (newLength == prevLength)
                            {
                                break;
                            }
                            prevLength = newLength;
                        }
                    }
                    Out.WriteLine("Done with rundown.");
                }

            Out.WriteLine("Zipping the raw files into a single '{0}' file.", dataFileName);

            // At this point you have multiple ETL files that don't have all the information
            // inside them necessary for analysis off the currentn machine.    To do analsysis
            // of the machine you need to merge the ETL files (which can be done with
            //        TraceEventSession.MergeInPlace(dataFileName, Out);
            // However this does not get the symbolic information (NGEN PDBS) needed to
            // decode the stacks in the .NET managed framewrok on another machine.
            // To do the merging AND generate these NGEN images it is best to us ethe
            // ZipppedETLWriter that does all this (and compresses all the files in a ZIP archive).

            ZippedETLWriter writer = new ZippedETLWriter(dataFileName, Out);

            writer.WriteArchive();

            Out.WriteLine("Zip complete, output file = {0}", writer.ZipArchivePath);
        }
        /// <summary>
        /// This is a demo of using TraceEvent to activate a 'real time' provider that is listening to
        /// the MyEventSource above.   Normally this event source would be in a different process,  but
        /// it also works if this process generate the events and I do that here for simplicity.
        /// </summary>
        public static int Run()
        {
            Out.WriteLine("******************** SimpleEventSourceMonitor DEMO ********************");
            Out.WriteLine("This program generates processes and displays EventSource events");
            Out.WriteLine("using the ETW REAL TIME pipeline. (thus no files are created)");
            Out.WriteLine();

            // Today you have to be Admin to turn on ETW events (anyone can write ETW events).
            if (!(TraceEventSession.IsElevated() ?? false))
            {
                Out.WriteLine("To turn on ETW events you need to be Administrator, please run from an Admin process.");
                Debugger.Break();
                return(-1);
            }

            // To listen to ETW events you need a session, which allows you to control which events will be produced
            // Note that it is the session and not the source that buffers events, and by default sessions will buffer
            // 64MB of events before dropping events.  Thus even if you don't immediately connect up the source and
            // read the events you should not lose them.
            //
            // As mentioned below, sessions can outlive the process that created them.  Thus you may need a way of
            // naming the session so that you can 'reconnect' to it from another process.   This is what the name
            // is for.  It can be anything, but it should be descriptive and unique.   If you expect multiple versions
            // of your program to run simultaneously, you need to generate unique names (e.g. add a process ID suffix)
            // however this is dangerous because you can leave data collection on if the program ends unexpectedly.
            var sessionName = "SimpleMontitorSession";

            Out.WriteLine("Creating a '{0}' session", sessionName);
            Out.WriteLine("Use 'logman query -ets' to see active sessions.");
            Out.WriteLine("Use 'logman stop {0} -ets' to manually stop orphans.", sessionName);
            using (var session = new TraceEventSession(sessionName))
            {
                /* BY DEFAULT ETW SESSIONS SURVIVE THE DEATH OF THE PROESS THAT CREATES THEM! */
                // Unlike most other resources on the system, ETW session live beyond the lifetime of the
                // process that created them.   This is very useful in some scenarios, but also creates the
                // very real possibility of leaving 'orphan' sessions running.
                //
                // To help avoid this by default TraceEventSession sets 'StopOnDispose' so that it will stop
                // the ETW session if the TraceEventSession dies.   Thus executions that 'clean up' the TraceEventSession
                // will clean up the ETW session.   This covers many cases (including throwing exceptions)
                //
                // However if the process is killed manually (including control C) this cleanup will not happen.
                // Thus best practices include
                //
                //     * Add a Control C handler that calls session.Dispose() so it gets cleaned up in this common case
                //     * use the same session name (say your program name) run-to-run so you don't create many orphans.
                //
                // By default TraceEventSessions are in 'create' mode where it assumes you want to create a new session.
                // In this mode if a session already exists, it is stopped and the new one is created.
                //
                // Here we install the Control C handler.   It is OK if Dispose is called more than once.
                Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { session.Dispose(); };

                // To demonstrate non-trivial event manipulation, we calculate the time delta between 'MyFirstEvent and 'MySecondEvent'
                // firstEventTimeMSec remembers all the 'MyFirstEvent' arrival times (indexed by their ID)
                var firstEventTimeMSec = new Dictionary <int, double>();

                /*****************************************************************************************************/
                // Hook up events.   To do this we first  need a 'Parser. which knows how to part the events of a particular Event Provider.
                // In this case we get a DynamicTraceEventSource, which knows how to parse any EventSource provider.    This parser
                // is so common, that TraceEventSource has a shortcut property called 'Dynamic' that fetches this parser.

                // For debugging, and demo purposes, hook up a callback for every event that 'Dynamic' knows about (this is not EVERY
                // event only those known about by DynamiceTraceEventParser).   However the 'UnhandledEvents' handler below will catch
                // the other ones.
                session.Source.Dynamic.All += delegate(TraceEvent data)
                {
                    // ETW buffers events and only delivers them after buffering up for some amount of time.  Thus
                    // there is a small delay of about 2-4 seconds between the timestamp on the event (which is very
                    // accurate), and the time we actually get the event.  We measure that delay here.
                    var delay = (DateTime.Now - data.TimeStamp).TotalSeconds;
                    Out.WriteLine("GOT Event Delay={0:f1}sec: {1} ", delay, data.ToString());
                };

                // Add logic on what to do when we get "MyFirstEvent"
                session.Source.Dynamic.AddCallbackForProviderEvent("Microsoft-Demos-SimpleMonitor", "MyFirstEvent", delegate(TraceEvent data)
                {
                    // On First Events, simply remember the ID and time of the event
                    firstEventTimeMSec[(int)data.PayloadByName("MyId")] = data.TimeStampRelativeMSec;
                });

                // Add logic on what to do when we get "MySecondEvent"
                session.Source.Dynamic.AddCallbackForProviderEvent("Microsoft-Demos-SimpleMonitor", "MySecondEvent", delegate(TraceEvent data)
                {
                    // On Second Events, if the ID matches, compute the delta and display it.
                    var myID = (int)data.PayloadByName("MyId");
                    double firstEventTime;
                    if (firstEventTimeMSec.TryGetValue(myID, out firstEventTime))
                    {
                        firstEventTimeMSec.Remove(myID);            // We are done with the ID after matching it, so remove it from the table.
                        Out.WriteLine("   >>> Time Delta from first Event = {0:f3} MSec", data.TimeStampRelativeMSec - firstEventTime);
                    }
                    else
                    {
                        Out.WriteLine("   >>> WARNING, Found a 'SecondEvent' without a corresponding 'FirstEvent'");
                    }
                });

                // Add logic on what to do when we get "Stop"
                session.Source.Dynamic.AddCallbackForProviderEvent("Microsoft-Demos-SimpleMonitor", "MyStopEvent", delegate(TraceEvent data)
                {
                    Out.WriteLine("    >>> Got a stop message");
                    // Stop processing after we we see the 'Stop' event, this will 'Process() to return.   It is OK to call Dispose twice
                    session.Source.Dispose();
                });

#if DEBUG
                // The callback above will only be called for events the parser recognizes (in the case of DynamicTraceEventParser, EventSources)
                // It is sometimes useful to see the other events that are not otherwise being handled.  The source knows about these and you
                // can ask the source to send them to you like this.
                session.Source.UnhandledEvents += delegate(TraceEvent data)
                {
                    if ((int)data.ID != 0xFFFE)         // The EventSource manifest events show up as unhanded, filter them out.
                    {
                        Out.WriteLine("GOT UNHANDLED EVENT: " + data.Dump());
                    }
                };
#endif
                // At this point we have created a TraceEventSession, hooked it up to a TraceEventSource, and hooked the
                // TraceEventSource to a TraceEventParser (you can do several of these), and then hooked up callbacks
                // up to the TraceEventParser (again you can have several).  However we have NOT actually told any
                // provider (EventSources) to actually send any events to our TraceEventSession.
                // We do that now.

                // Enable my provider, you can call many of these on the same session to get events from other providers.
                // Because this EventSource did not define any keywords, I can only turn on all events or none.
                var restarted = session.EnableProvider("Microsoft-Demos-SimpleMonitor");
                if (restarted)      // Generally you don't bother with this warning, but for the demo we do.
                {
                    Out.WriteLine("The session {0} was already active, it has been restarted.", sessionName);
                }

                // Start another thread that Causes MyEventSource to create some events
                // Normally this code as well as the EventSource itself would be in a different process.
                EventGenerator.CreateEvents();

                Out.WriteLine("**** Start listening for events from the Microsoft-Demos-SimpleMonitor provider.");
                // go into a loop processing events can calling the callbacks.  Because this is live data (not from a file)
                // processing never completes by itself, but only because someone called 'source.Dispose()'.
                session.Source.Process();
                Out.WriteLine();
                Out.WriteLine("Stopping the collection of events.");
            }
            return(0);
        }
Пример #16
0
        public async Task Start(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    var options = new EventHubProducerClientOptions
                    {
                        RetryOptions = new EventHubsRetryOptions
                        {
                            TryTimeout = Configuration.SendTimeout
                        }
                    };

                    var producer = new EventHubProducerClient(Configuration.EventHubsConnectionString, Configuration.EventHub, options);

                    await using (producer.ConfigureAwait(false))
                    {
                        while (!cancellationToken.IsCancellationRequested)
                        {
                            // Query partitions each iteration to allow any new partitions to be discovered. Dynamic upscaling of partitions
                            // is a near-term feature being added to the Event Hubs service.

                            var partitions = await producer.GetPartitionIdsAsync(cancellationToken).ConfigureAwait(false);

                            var selectedPartition = partitions[RandomNumberGenerator.Value.Next(0, partitions.Length)];
                            var batchEvents       = new List <EventData>();

                            // Create the batch and generate a set of random events, keeping only those that were able to fit into the batch.

                            using var batch = await producer.CreateBatchAsync(new CreateBatchOptions { PartitionId = selectedPartition }).ConfigureAwait(false);

                            var events = EventGenerator.CreateEvents(
                                Configuration.PublishBatchSize,
                                Configuration.LargeMessageRandomFactorPercent,
                                Configuration.PublishingBodyMinBytes,
                                Configuration.PublishingBodyRegularMaxBytes,
                                currentSequence,
                                selectedPartition);

                            foreach (var currentEvent in events)
                            {
                                if (!batch.TryAdd(currentEvent))
                                {
                                    break;
                                }

                                batchEvents.Add(currentEvent);
                            }

                            // Publish the events and report them, capturing any failures specific to the send operation.

                            try
                            {
                                if (batch.Count > 0)
                                {
                                    await producer.SendAsync(batch, cancellationToken).ConfigureAwait(false);

                                    foreach (var batchEvent in batchEvents)
                                    {
                                        batchEvent.Properties.TryGetValue(EventGenerator.IdPropertyName, out var id);
                                        PublishedEvents.AddOrUpdate(id.ToString(), _ => batchEvent, (k, v) => batchEvent);
                                    }

                                    Interlocked.Add(ref Metrics.EventsPublished, batch.Count);
                                    Interlocked.Increment(ref Metrics.TotalServiceOperations);
                                }
                            }
                            catch (TaskCanceledException)
                            {
                            }
                            catch (EventHubsException ex)
                            {
                                Interlocked.Increment(ref Metrics.TotalExceptions);
                                Interlocked.Increment(ref Metrics.SendExceptions);
                                ex.TrackMetrics(Metrics);
                                ErrorsObserved.Add(ex);
                            }
                            catch (Exception ex)
                            {
                                Interlocked.Increment(ref Metrics.TotalExceptions);
                                Interlocked.Increment(ref Metrics.SendExceptions);
                                Interlocked.Increment(ref Metrics.GeneralExceptions);
                                ErrorsObserved.Add(ex);
                            }

                            // Honor any requested delays to throttle publishing so that it doesn't overwhelm slower consumers.

                            if ((Configuration.PublishingDelay.HasValue) && (Configuration.PublishingDelay.Value > TimeSpan.Zero))
                            {
                                await Task.Delay(Configuration.PublishingDelay.Value).ConfigureAwait(false);
                            }
                        }
                    }
                }
                catch (TaskCanceledException)
                {
                    // No action needed.
                }
                catch (Exception ex) when
                    (ex is OutOfMemoryException ||
                    ex is StackOverflowException ||
                    ex is ThreadAbortException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    Interlocked.Increment(ref Metrics.ProducerRestarted);
                    ErrorsObserved.Add(ex);
                }
            }
        }
Пример #17
0
        private async Task PerformSend(EventHubProducerClient producer,
                                       CancellationToken cancellationToken)
        {
            // Create the batch and generate a set of random events, keeping only those that were able to fit into the batch.
            // Because there is a side-effect of TryAdd in the statement, ensure that ToList is called to materialize the set
            // or the batch will be empty at send.

            using var batch = await producer.CreateBatchAsync().ConfigureAwait(false);

            var batchEvents = new List <EventData>();

            var events = EventGenerator.CreateEvents(
                Configuration.PublishBatchSize,
                Configuration.LargeMessageRandomFactorPercent,
                Configuration.PublishingBodyMinBytes,
                Configuration.PublishingBodyRegularMaxBytes,
                currentSequence);

            foreach (var currentEvent in events)
            {
                if (!batch.TryAdd(currentEvent))
                {
                    break;
                }

                batchEvents.Add(currentEvent);
            }

            // Publish the events and report them, capturing any failures specific to the send operation.

            try
            {
                if (batch.Count > 0)
                {
                    Interlocked.Increment(ref Metrics.PublishAttempts);
                    await producer.SendAsync(batch, cancellationToken).ConfigureAwait(false);

                    Interlocked.Increment(ref Metrics.BatchesPublished);
                    Interlocked.Add(ref Metrics.EventsPublished, batch.Count);
                    Interlocked.Add(ref Metrics.TotalPublshedSizeBytes, batch.SizeInBytes);
                }
            }
            catch (TaskCanceledException)
            {
                Interlocked.Decrement(ref Metrics.PublishAttempts);
            }
            catch (EventHubsException ex)
            {
                Interlocked.Increment(ref Metrics.TotalExceptions);
                Interlocked.Increment(ref Metrics.SendExceptions);
                ex.TrackMetrics(Metrics);
                ErrorsObserved.Add(ex);
            }
            catch (OperationCanceledException ex)
            {
                Interlocked.Increment(ref Metrics.TotalExceptions);
                Interlocked.Increment(ref Metrics.SendExceptions);
                Interlocked.Increment(ref Metrics.CanceledSendExceptions);
                ErrorsObserved.Add(ex);
            }
            catch (TimeoutException ex)
            {
                Interlocked.Increment(ref Metrics.TotalExceptions);
                Interlocked.Increment(ref Metrics.SendExceptions);
                Interlocked.Increment(ref Metrics.TimeoutExceptions);
                ErrorsObserved.Add(ex);
            }
            catch (Exception ex)
            {
                Interlocked.Increment(ref Metrics.TotalExceptions);
                Interlocked.Increment(ref Metrics.SendExceptions);
                Interlocked.Increment(ref Metrics.GeneralExceptions);
                ErrorsObserved.Add(ex);
            }
        }