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."); } }
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."); } }
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); }
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); } }
/// <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."); } }
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."); } } }
/// <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); }
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); } } }
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); } }