Skip to content

Commit

Permalink
Fire an ETW event when the background document generator queue is emp…
Browse files Browse the repository at this point in the history
…ty (#11233)

I was musing the other day about ETW events possibly being useful in
tracking down the Speedometer regression I was looking in to, and then
along comes #11232 which is a
great excuse to add some ETW event infrastructure to our lives.

The idea here is that RPS/Speedometer tests can wait for Razor to be
"ready" by looking for this event, rather than `Thread.Sleep`ing.
Anecdotally on my machine, when loading OrchardCore, this event fires
once a couple of minutes after solution load, so seems like a reasonable
candidate.

Once this is in @WardenGnaw will try it out and if it's not good, we'll
pop some more events somewhere.

Will need to follow up with PR(s?) to add this event source name to the
list, but I need to work out where to do that first :)
  • Loading branch information
davidwengier authored Nov 21, 2024
2 parents 2ce77ad + 1b811b3 commit 2916c40
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Diagnostics.Tracing;

namespace Microsoft.AspNetCore.Razor;

[EventSource(Name = "RazorEventSource")]
internal sealed class RazorEventSource : EventSource
{
public static readonly RazorEventSource Instance = new();

private RazorEventSource()
{
}

[Event(1, Level = EventLevel.Informational)]
public void BackgroundDocumentGeneratorIdle() => WriteEvent(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ internal class AsyncBatchingWorkQueue<TItem>(
TimeSpan delay,
Func<ImmutableArray<TItem>, CancellationToken, ValueTask> processBatchAsync,
IEqualityComparer<TItem>? equalityComparer,
CancellationToken cancellationToken) : AsyncBatchingWorkQueue<TItem, VoidResult>(delay, Convert(processBatchAsync), equalityComparer, cancellationToken)
Action? idleAction,
CancellationToken cancellationToken) : AsyncBatchingWorkQueue<TItem, VoidResult>(delay, Convert(processBatchAsync), equalityComparer, idleAction, cancellationToken)
{
public AsyncBatchingWorkQueue(
TimeSpan delay,
Expand All @@ -30,6 +31,19 @@ public AsyncBatchingWorkQueue(
{
}

public AsyncBatchingWorkQueue(
TimeSpan delay,
Func<ImmutableArray<TItem>, CancellationToken, ValueTask> processBatchAsync,
IEqualityComparer<TItem>? equalityComparer,
CancellationToken cancellationToken)
: this(delay,
processBatchAsync,
equalityComparer,
idleAction: null,
cancellationToken)
{
}

private static Func<ImmutableArray<TItem>, CancellationToken, ValueTask<VoidResult>> Convert(Func<ImmutableArray<TItem>, CancellationToken, ValueTask> processBatchAsync)
=> async (items, ct) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ internal class AsyncBatchingWorkQueue<TItem, TResult>
/// </summary>
private readonly IEqualityComparer<TItem>? _equalityComparer;

/// <summary>
/// Fired when all batches have finished being processed, and the queue is waiting for an AddWork call.
/// </summary>
/// <remarks>
/// This is a best-effort signal with no guarantee that more work won't be queued, and hence the queue
/// going non-idle, immediately after (or during!) the event firing.
/// </remarks>
private readonly Action? _idleAction;

/// <summary>
/// Callback to actually perform the processing of the next batch of work.
/// </summary>
Expand Down Expand Up @@ -106,11 +115,13 @@ public AsyncBatchingWorkQueue(
TimeSpan delay,
Func<ImmutableArray<TItem>, CancellationToken, ValueTask<TResult>> processBatchAsync,
IEqualityComparer<TItem>? equalityComparer,
Action? idleAction,
CancellationToken cancellationToken)
{
_delay = delay;
_processBatchAsync = processBatchAsync;
_equalityComparer = equalityComparer;
_idleAction = idleAction;
_entireQueueCancellationToken = cancellationToken;

_uniqueItems = new HashSet<TItem>(equalityComparer);
Expand Down Expand Up @@ -210,7 +221,17 @@ void AddItemsToBatch(IEnumerable<TItem> items)
// then reset that bool back to false
await Task.Yield().ConfigureAwait(false);
await Task.Delay(_delay, _entireQueueCancellationToken).ConfigureAwait(false);
return await ProcessNextBatchAsync().ConfigureAwait(false);
var result = await ProcessNextBatchAsync().ConfigureAwait(false);

// Not worried about the lock here because we don't want to fire the event under the lock, which means
// there is no effective way to avoid a race. The event doesn't guarantee that there will never be any
// more work anyway, it's merely a best effort.
if (_idleAction is { } idleAction && _nextBatch.Count == 0)
{
idleAction();
}

return result;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ protected BackgroundDocumentGenerator(
_logger = loggerFactory.GetOrCreateLogger<BackgroundDocumentGenerator>();

_disposeTokenSource = new();
_workQueue = new AsyncBatchingWorkQueue<(IProjectSnapshot, IDocumentSnapshot)>(delay, ProcessBatchAsync, _disposeTokenSource.Token);
_workQueue = new AsyncBatchingWorkQueue<(IProjectSnapshot, IDocumentSnapshot)>(
delay,
processBatchAsync: ProcessBatchAsync,
equalityComparer: null,
idleAction: RazorEventSource.Instance.BackgroundDocumentGeneratorIdle,
_disposeTokenSource.Token);
_suppressedDocuments = ImmutableHashSet<string>.Empty.WithComparer(FilePathComparer.Instance);
_projectManager.Changed += ProjectManager_Changed;
}
Expand Down

0 comments on commit 2916c40

Please sign in to comment.