Skip to content

Commit e863401

Browse files
author
evgenyfedorov2
committed
Flush in batches and pool lists for emitted records
1 parent 4248980 commit e863401

File tree

4 files changed

+119
-23
lines changed

4 files changed

+119
-23
lines changed

src/Libraries/Microsoft.AspNetCore.Diagnostics.Middleware/Buffering/IncomingRequestLogBuffer.cs

+35-12
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99
using System.Threading;
1010
using Microsoft.Extensions.Diagnostics.Buffering;
1111
using Microsoft.Extensions.Logging.Abstractions;
12+
using Microsoft.Extensions.ObjectPool;
1213
using Microsoft.Extensions.Options;
1314
using Microsoft.Shared.Diagnostics;
15+
using Microsoft.Shared.Pools;
1416

1517
namespace Microsoft.AspNetCore.Diagnostics.Buffering;
1618

1719
internal sealed class IncomingRequestLogBuffer
1820
{
21+
private const int MaxBatchSize = 256;
22+
private static readonly ObjectPool<List<DeserializedLogRecord>> _recordsToEmitListPool =
23+
PoolFactory.CreateListPoolWithCapacity<DeserializedLogRecord>(MaxBatchSize);
24+
1925
private readonly IBufferedLogger _bufferedLogger;
2026
private readonly LogBufferingFilterRuleSelector _ruleSelector;
2127
private readonly IOptionsMonitor<PerRequestLogBufferingOptions> _options;
@@ -107,21 +113,38 @@ public void Flush()
107113
}
108114
SerializedLogRecord[] bufferedRecords = bufferToFlush.ToArray();
109115

110-
111-
var recordsToEmit = new List<DeserializedLogRecord>(bufferedRecords.Length);
112-
foreach (SerializedLogRecord bufferedRecord in bufferedRecords)
116+
// Process records in batches
117+
for (int offset = 0; offset < bufferedRecords.Length; offset += MaxBatchSize)
113118
{
114-
recordsToEmit.Add(new DeserializedLogRecord(
115-
bufferedRecord.Timestamp,
116-
bufferedRecord.LogLevel,
117-
bufferedRecord.EventId,
118-
bufferedRecord.Exception,
119-
bufferedRecord.FormattedMessage,
120-
bufferedRecord.Attributes));
119+
int currentBatchSize = Math.Min(MaxBatchSize, bufferedRecords.Length - offset);
120+
List<DeserializedLogRecord>? recordsToEmit = null;
121+
try
122+
{
123+
recordsToEmit = _recordsToEmitListPool.Get();
124+
125+
for (int i = 0; i < currentBatchSize; i++)
126+
{
127+
SerializedLogRecord bufferedRecord = bufferedRecords[offset + i];
128+
recordsToEmit.Add(new DeserializedLogRecord(
129+
bufferedRecord.Timestamp,
130+
bufferedRecord.LogLevel,
131+
bufferedRecord.EventId,
132+
bufferedRecord.Exception,
133+
bufferedRecord.FormattedMessage,
134+
bufferedRecord.Attributes));
135+
}
136+
137+
_bufferedLogger.LogRecords(recordsToEmit);
138+
}
139+
finally
140+
{
141+
if(recordsToEmit is not null)
142+
{
143+
_recordsToEmitListPool.Return(recordsToEmit);
144+
}
145+
}
121146
}
122147

123-
_bufferedLogger.LogRecords(recordsToEmit);
124-
125148
SerializedLogRecordFactory.Return(bufferedRecords);
126149
}
127150

src/Libraries/Microsoft.Extensions.Telemetry/Buffering/GlobalBuffer.cs

+35-11
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@
88
using System.Linq;
99
using System.Threading;
1010
using Microsoft.Extensions.Logging.Abstractions;
11+
using Microsoft.Extensions.ObjectPool;
1112
using Microsoft.Extensions.Options;
1213
using Microsoft.Shared.Diagnostics;
14+
using Microsoft.Shared.Pools;
1315

1416
namespace Microsoft.Extensions.Diagnostics.Buffering;
1517

1618
internal sealed class GlobalBuffer : IDisposable
1719
{
20+
private const int MaxBatchSize = 256;
21+
private static readonly ObjectPool<List<DeserializedLogRecord>> _recordsToEmitListPool =
22+
PoolFactory.CreateListPoolWithCapacity<DeserializedLogRecord>(MaxBatchSize);
23+
1824
private readonly IOptionsMonitor<GlobalLogBufferingOptions> _options;
1925
private readonly IBufferedLogger _bufferedLogger;
2026
private readonly TimeProvider _timeProvider;
@@ -127,20 +133,38 @@ public void Flush()
127133

128134
SerializedLogRecord[] bufferedRecords = bufferToFlush.ToArray();
129135

130-
var recordsToEmit = new List<DeserializedLogRecord>(bufferedRecords.Length);
131-
foreach (SerializedLogRecord bufferedRecord in bufferedRecords)
136+
// Process records in batches
137+
for (int offset = 0; offset < bufferedRecords.Length; offset += MaxBatchSize)
132138
{
133-
recordsToEmit.Add(new DeserializedLogRecord(
134-
bufferedRecord.Timestamp,
135-
bufferedRecord.LogLevel,
136-
bufferedRecord.EventId,
137-
bufferedRecord.Exception,
138-
bufferedRecord.FormattedMessage,
139-
bufferedRecord.Attributes));
139+
int currentBatchSize = Math.Min(MaxBatchSize, bufferedRecords.Length - offset);
140+
List<DeserializedLogRecord>? recordsToEmit = null;
141+
try
142+
{
143+
recordsToEmit = _recordsToEmitListPool.Get();
144+
145+
for (int i = 0; i < currentBatchSize; i++)
146+
{
147+
SerializedLogRecord bufferedRecord = bufferedRecords[offset + i];
148+
recordsToEmit.Add(new DeserializedLogRecord(
149+
bufferedRecord.Timestamp,
150+
bufferedRecord.LogLevel,
151+
bufferedRecord.EventId,
152+
bufferedRecord.Exception,
153+
bufferedRecord.FormattedMessage,
154+
bufferedRecord.Attributes));
155+
}
156+
157+
_bufferedLogger.LogRecords(recordsToEmit);
158+
}
159+
finally
160+
{
161+
if(recordsToEmit is not null)
162+
{
163+
_recordsToEmitListPool.Return(recordsToEmit);
164+
}
165+
}
140166
}
141167

142-
_bufferedLogger.LogRecords(recordsToEmit);
143-
144168
SerializedLogRecordFactory.Return(bufferedRecords);
145169
}
146170

src/Shared/Pools/PoolFactory.cs

+19
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,25 @@ public static ObjectPool<List<T>> CreateListPool<T>(int maxCapacity = DefaultCap
118118
return MakePool(PooledListPolicy<T>.Instance, maxCapacity);
119119
}
120120

121+
/// <summary>
122+
/// Creates an object pool of <see cref="List{T}"/> instances, each with provided <see cref="listCapacity"/>.
123+
/// </summary>
124+
/// <typeparam name="T">The type of object held by the lists.</typeparam>
125+
/// <param name="listCapacity">The capacity of each created <see cref="List{T}"/> instance.</param>
126+
/// <param name="maxCapacity">
127+
/// The maximum number of items to keep in the pool.
128+
/// This defaults to 1024.
129+
/// This value is a recommendation, the pool may keep more objects than this.
130+
/// </param>
131+
/// <returns>The pool.</returns>
132+
public static ObjectPool<List<T>> CreateListPoolWithCapacity<T>(int listCapacity, int maxCapacity = DefaultCapacity)
133+
{
134+
_ = Throw.IfLessThan(maxCapacity, 1);
135+
_ = Throw.IfLessThan(listCapacity, 0);
136+
137+
return MakePool(PooledListWithCapacityPolicy<T>.Instance(listCapacity), maxCapacity);
138+
}
139+
121140
/// <summary>
122141
/// Creates an object pool of <see cref="Dictionary{TKey, TValue}"/> instances.
123142
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.Extensions.ObjectPool;
6+
7+
namespace Microsoft.Shared.Pools;
8+
9+
/// <summary>
10+
/// An object pool policy for lists with capacity.
11+
/// </summary>
12+
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
13+
internal sealed class PooledListWithCapacityPolicy<T> : PooledObjectPolicy<List<T>>
14+
{
15+
private readonly int _listCapacity;
16+
public static PooledListWithCapacityPolicy<T> Instance (int listCapacity) => new(listCapacity);
17+
18+
private PooledListWithCapacityPolicy(int listCapacity)
19+
{
20+
_listCapacity = listCapacity;
21+
}
22+
23+
public override List<T> Create() => new(_listCapacity);
24+
25+
public override bool Return(List<T> obj)
26+
{
27+
obj.Clear();
28+
return true;
29+
}
30+
}

0 commit comments

Comments
 (0)