Skip to content

Commit

Permalink
EventHub EventSource remove allocations and boxing (#26989)
Browse files Browse the repository at this point in the history
* Stackalloc event data in EventSource

* Provide a few custom implementations

* More method overloads

* Introduce a more generic approach

* Move private members down

* XML Docs for some, bring methods over into sources that need it

* More XML doc

* Bring back glorified formatting

* Move event id to no longer break locality

* Remove TODO

* Update change logs and apply minor formatting changes and comment headers

Co-authored-by: Jesse Squire <jesse.squire@gmail.com>
Co-authored-by: Jesse Squire <jsquire@microsoft.com>
  • Loading branch information
3 people committed Feb 23, 2022
1 parent e982e98 commit 02952d3
Show file tree
Hide file tree
Showing 7 changed files with 928 additions and 21 deletions.
8 changes: 8 additions & 0 deletions sdk/eventhub/Azure.Messaging.EventHubs.Processor/CHANGELOG.md
Expand Up @@ -2,6 +2,12 @@

## 5.7.0-beta.4 (Unreleased)

### Acknowledgments

Thank you to our developer community members who helped to make the Event Hubs client libraries better with their contributions to this release:

- Daniel Marbach _([GitHub](https://github.com/danielmarbach))_

### Features Added

### Breaking Changes
Expand All @@ -10,6 +16,8 @@

### Other Changes

- Remove allocations from Event Source logging by introducing `WriteEvent` overloads to handle cases that would otherwise result in boxing to `object[]` via params array. _(A community contribution, courtesy of [danielmarbach](https://github.com/danielmarbach))_

## 5.7.0-beta.3 (2022-02-09)

### Features Added
Expand Down
Expand Up @@ -6,6 +6,7 @@
<ApiCompatVersion>5.6.2</ApiCompatVersion>
<PackageTags>Azure;Event Hubs;EventHubs;.NET;Event Processor;EventProcessor;$(PackageCommonTags)</PackageTags>
<TargetFrameworks>$(RequiredTargetFrameworks)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using Azure.Core.Diagnostics;

namespace Azure.Messaging.EventHubs.Processor.Diagnostics
Expand All @@ -11,7 +13,8 @@ namespace Azure.Messaging.EventHubs.Processor.Diagnostics
/// </summary>
///
/// <remarks>
/// When defining Start/Stop tasks, the StopEvent.Id must be exactly StartEvent.Id + 1.
/// When defining Start/Stop tasks, it is strongly recommended that the StopEvent.Id be
/// exactly StartEvent.Id + 1.
///
/// Do not explicitly include the Guid here, since EventSource has a mechanism to automatically
/// map to an EventSource Guid based on the Name (Azure-Messaging-EventHubs-Processor-BlobEventStore).
Expand Down Expand Up @@ -396,9 +399,9 @@ protected BlobEventStoreEventSource() : base(EventSourceName)
///
[Event(36, Level = EventLevel.Verbose, Message = "Starting to retrieve checkpoint for FullyQualifiedNamespace: '{0}'; EventHubName: '{1}'; ConsumerGroup: '{2}'; PartitionId: '{3}'.")]
public virtual void GetCheckpointStart(string fullyQualifiedNamespace,
string eventHubName,
string consumerGroup,
string partitionId)
string eventHubName,
string consumerGroup,
string partitionId)
{
if (IsEnabled())
{
Expand All @@ -417,9 +420,9 @@ protected BlobEventStoreEventSource() : base(EventSourceName)
///
[Event(37, Level = EventLevel.Verbose, Message = "Completed retrieving checkpoint for FullyQualifiedNamespace: '{0}'; EventHubName: '{1}'; ConsumerGroup: '{2}'. PartitionId: '{3}'.")]
public virtual void GetCheckpointComplete(string fullyQualifiedNamespace,
string eventHubName,
string consumerGroup,
string partitionId)
string eventHubName,
string consumerGroup,
string partitionId)
{
if (IsEnabled())
{
Expand All @@ -439,15 +442,200 @@ protected BlobEventStoreEventSource() : base(EventSourceName)
///
[Event(38, Level = EventLevel.Error, Message = "An exception occurred when retrieving checkpoint for FullyQualifiedNamespace: '{0}'; EventHubName: '{1}'; ConsumerGroup: '{2}'; PartitionId: '{3}'; ErrorMessage: '{4}'.")]
public virtual void GetCheckpointError(string fullyQualifiedNamespace,
string eventHubName,
string consumerGroup,
string partitionId,
string errorMessage)
string eventHubName,
string consumerGroup,
string partitionId,
string errorMessage)
{
if (IsEnabled())
{
WriteEvent(38, fullyQualifiedNamespace ?? string.Empty, eventHubName ?? string.Empty, consumerGroup ?? string.Empty, partitionId ?? string.Empty, errorMessage ?? string.Empty);
}
}

/// <summary>
/// Writes an event with three string arguments and a value type argument into a stack allocated
/// <see cref="EventSource.EventData"/> struct to avoid the parameter array allocation on the WriteEvent methods.
/// </summary>
///
/// <param name="eventId">The identifier of the event.</param>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <param name="arg3">The third argument.</param>
/// <param name="arg4">The fourth argument.</param>
///
[NonEvent]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteEvent<TValue1, TValue2>(int eventId,
string arg1,
string arg2,
string arg3,
TValue2 arg4)
where TValue1 : struct
{
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
{
var eventPayload = stackalloc EventData[4];

eventPayload[0].Size = (arg1.Length + 1) * sizeof(char);
eventPayload[0].DataPointer = (IntPtr)arg1Ptr;

eventPayload[1].Size = (arg2.Length + 1) * sizeof(char);
eventPayload[1].DataPointer = (IntPtr)arg2Ptr;

eventPayload[2].Size = (arg3.Length + 1) * sizeof(char);
eventPayload[2].DataPointer = (IntPtr)arg3Ptr;

eventPayload[3].Size = Unsafe.SizeOf<TValue1>();
eventPayload[3].DataPointer = (IntPtr)Unsafe.AsPointer(ref arg4);

WriteEventCore(eventId, 4, eventPayload);
}
}

/// <summary>
/// Writes an event with four string arguments into a stack allocated <see cref="EventSource.EventData"/> struct
/// to avoid the parameter array allocation on the WriteEvent methods.
/// </summary>
///
/// <param name="eventId">The identifier of the event.</param>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <param name="arg3">The third argument.</param>
/// <param name="arg4">The fourth argument.</param>
///
[NonEvent]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteEvent(int eventId,
string arg1,
string arg2,
string arg3,
string arg4)
{
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
fixed (char* arg4Ptr = arg4)
{
var eventPayload = stackalloc EventData[4];

eventPayload[0].Size = (arg1.Length + 1) * sizeof(char);
eventPayload[0].DataPointer = (IntPtr)arg1Ptr;

eventPayload[1].Size = (arg2.Length + 1) * sizeof(char);
eventPayload[1].DataPointer = (IntPtr)arg2Ptr;

eventPayload[2].Size = (arg3.Length + 1) * sizeof(char);
eventPayload[2].DataPointer = (IntPtr)arg3Ptr;

eventPayload[3].Size = (arg4.Length + 1) * sizeof(char);
eventPayload[3].DataPointer = (IntPtr)arg4Ptr;

WriteEventCore(eventId, 4, eventPayload);
}
}
/// <summary>
/// Writes an event with five string arguments into a stack allocated
/// <see cref="EventSource.EventData"/> struct to avoid the parameter array allocation on the WriteEvent methods.
/// </summary>
///
/// <param name="eventId">The identifier of the event.</param>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <param name="arg3">The third argument.</param>
/// <param name="arg4">The fourth argument.</param>
/// <param name="arg5">The fifth argument.</param>
///
[NonEvent]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteEvent(int eventId,
string arg1,
string arg2,
string arg3,
string arg4,
string arg5)
{
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
fixed (char* arg4Ptr = arg4)
fixed (char* arg5Ptr = arg5)
{
var eventPayload = stackalloc EventData[5];

eventPayload[0].Size = (arg1.Length + 1) * sizeof(char);
eventPayload[0].DataPointer = (IntPtr)arg1Ptr;

eventPayload[1].Size = (arg2.Length + 1) * sizeof(char);
eventPayload[1].DataPointer = (IntPtr)arg2Ptr;

eventPayload[2].Size = (arg3.Length + 1) * sizeof(char);
eventPayload[2].DataPointer = (IntPtr)arg3Ptr;

eventPayload[3].Size = (arg4.Length + 1) * sizeof(char);
eventPayload[3].DataPointer = (IntPtr)arg4Ptr;

eventPayload[4].Size = (arg5.Length + 1) * sizeof(char);
eventPayload[4].DataPointer = (IntPtr)arg5Ptr;

WriteEventCore(eventId, 5, eventPayload);
}
}

/// <summary>
/// Writes an event with six string arguments into a stack allocated <see cref="EventSource.EventData"/> struct
/// to avoid the parameter array allocation on the WriteEvent methods.
/// </summary>
///
/// <param name="eventId">The identifier of the event.</param>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <param name="arg3">The third argument.</param>
/// <param name="arg4">The fourth argument.</param>
/// <param name="arg5">The fifth argument.</param>
/// <param name="arg6">The sixth argument.</param>
///
[NonEvent]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteEvent(int eventId,
string arg1,
string arg2,
string arg3,
string arg4,
string arg5,
string arg6)
{
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
fixed (char* arg4Ptr = arg4)
fixed (char* arg5Ptr = arg5)
fixed (char* arg6Ptr = arg6)
{
var eventPayload = stackalloc EventData[6];

eventPayload[0].Size = (arg1.Length + 1) * sizeof(char);
eventPayload[0].DataPointer = (IntPtr)arg1Ptr;

eventPayload[1].Size = (arg2.Length + 1) * sizeof(char);
eventPayload[1].DataPointer = (IntPtr)arg2Ptr;

eventPayload[2].Size = (arg3.Length + 1) * sizeof(char);
eventPayload[2].DataPointer = (IntPtr)arg3Ptr;

eventPayload[3].Size = (arg4.Length + 1) * sizeof(char);
eventPayload[3].DataPointer = (IntPtr)arg4Ptr;

eventPayload[4].Size = (arg5.Length + 1) * sizeof(char);
eventPayload[4].DataPointer = (IntPtr)arg5Ptr;

eventPayload[5].Size = (arg6.Length + 1) * sizeof(char);
eventPayload[5].DataPointer = (IntPtr)arg6Ptr;

WriteEventCore(eventId, 6, eventPayload);
}
}
}
}
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics.Tracing;
using System.Runtime.CompilerServices;
using Azure.Core.Diagnostics;

namespace Azure.Messaging.EventHubs.Processor.Diagnostics
Expand Down Expand Up @@ -188,5 +190,95 @@ protected EventProcessorClientEventSource() : base(EventSourceName)
WriteEvent(26, identifier ?? string.Empty, eventHubName ?? string.Empty, consumerGroup ?? string.Empty, errorMessage ?? string.Empty);
}
}

/// <summary>
/// Writes an event with four string arguments into a stack allocated <see cref="EventSource.EventData"/> struct
/// to avoid the parameter array allocation on the WriteEvent methods.
/// </summary>
///
/// <param name="eventId">The identifier of the event.</param>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <param name="arg3">The third argument.</param>
/// <param name="arg4">The fourth argument.</param>
///
[NonEvent]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteEvent(int eventId,
string arg1,
string arg2,
string arg3,
string arg4)
{
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
fixed (char* arg4Ptr = arg4)
{
var eventPayload = stackalloc EventData[4];

eventPayload[0].Size = (arg1.Length + 1) * sizeof(char);
eventPayload[0].DataPointer = (IntPtr)arg1Ptr;

eventPayload[1].Size = (arg2.Length + 1) * sizeof(char);
eventPayload[1].DataPointer = (IntPtr)arg2Ptr;

eventPayload[2].Size = (arg3.Length + 1) * sizeof(char);
eventPayload[2].DataPointer = (IntPtr)arg3Ptr;

eventPayload[3].Size = (arg4.Length + 1) * sizeof(char);
eventPayload[3].DataPointer = (IntPtr)arg4Ptr;

WriteEventCore(eventId, 4, eventPayload);
}
}

/// <summary>
/// Writes an event with five string arguments into a stack allocated
/// <see cref="EventSource.EventData"/> struct to avoid the parameter array allocation on the WriteEvent methods.
/// </summary>
///
/// <param name="eventId">The identifier of the event.</param>
/// <param name="arg1">The first argument.</param>
/// <param name="arg2">The second argument.</param>
/// <param name="arg3">The third argument.</param>
/// <param name="arg4">The fourth argument.</param>
/// <param name="arg5">The fifth argument.</param>
///
[NonEvent]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void WriteEvent(int eventId,
string arg1,
string arg2,
string arg3,
string arg4,
string arg5)
{
fixed (char* arg1Ptr = arg1)
fixed (char* arg2Ptr = arg2)
fixed (char* arg3Ptr = arg3)
fixed (char* arg4Ptr = arg4)
fixed (char* arg5Ptr = arg5)
{
var eventPayload = stackalloc EventData[5];

eventPayload[0].Size = (arg1.Length + 1) * sizeof(char);
eventPayload[0].DataPointer = (IntPtr)arg1Ptr;

eventPayload[1].Size = (arg2.Length + 1) * sizeof(char);
eventPayload[1].DataPointer = (IntPtr)arg2Ptr;

eventPayload[2].Size = (arg3.Length + 1) * sizeof(char);
eventPayload[2].DataPointer = (IntPtr)arg3Ptr;

eventPayload[3].Size = (arg4.Length + 1) * sizeof(char);
eventPayload[3].DataPointer = (IntPtr)arg4Ptr;

eventPayload[4].Size = (arg5.Length + 1) * sizeof(char);
eventPayload[4].DataPointer = (IntPtr)arg5Ptr;

WriteEventCore(eventId, 5, eventPayload);
}
}
}
}
4 changes: 3 additions & 1 deletion sdk/eventhub/Azure.Messaging.EventHubs/CHANGELOG.md
Expand Up @@ -16,7 +16,9 @@ Thank you to our developer community members who helped to make the Event Hubs c

### Other Changes

- Attempt to retrieve AMQP objects synchronously before calling `GetOrCreateAsync`.
- Attempt to retrieve AMQP objects synchronously before calling `GetOrCreateAsync`, avoiding an asynchronous call unless necessary.

- Remove allocations from Event Source logging by introducing `WriteEvent` overloads to handle cases that would otherwise result in boxing to `object[]` via params array. _(A community contribution, courtesy of [danielmarbach](https://github.com/danielmarbach))_

- Remove LINQ from the `AmqpMessageConverter` in favor of direct looping. _(Based on a community contribution, courtesy of [danielmarbach](https://github.com/danielmarbach))_

Expand Down

0 comments on commit 02952d3

Please sign in to comment.