Skip to content

Commit 452b749

Browse files
committed
use StringBuilder to build responses instead of List<string>
1 parent db8b40e commit 452b749

File tree

2 files changed

+97
-51
lines changed

2 files changed

+97
-51
lines changed

src/Smdn.Net.MuninNode/Smdn.Net.MuninNode.Protocol/MuninProtocolHandler.cs

Lines changed: 82 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
77
using System.Linq;
8-
#if SYSTEM_TEXT_ENCODINGEXTENSIONS
98
using System.Text;
10-
#endif
119
using System.Threading;
1210
using System.Threading.Tasks;
1311

@@ -57,9 +55,9 @@ private sealed class ArrayBufferWriterPool(int initialCapacity)
5755
) {
5856
}
5957

60-
private sealed class StringListPool(int initialCapacity)
61-
: ObjectPool<List<string>>(
62-
create: () => new List<string>(capacity: initialCapacity),
58+
private sealed class StringBuilderPool(int initialCapacity)
59+
: ObjectPool<StringBuilder>(
60+
create: () => new StringBuilder(capacity: initialCapacity),
6361
clear: item => item.Clear()
6462
) {
6563
}
@@ -68,7 +66,7 @@ private sealed class StringListPool(int initialCapacity)
6866
private readonly string banner;
6967
private readonly string versionInformation;
7068
private readonly ArrayBufferWriterPool bufferWriterPool = new(initialCapacity: 256);
71-
private readonly StringListPool responseLineListPool = new(initialCapacity: 32);
69+
private readonly StringBuilderPool responseBuilderPool = new(initialCapacity: 512);
7270
private readonly Dictionary<string, IPlugin> plugins = new(StringComparer.Ordinal);
7371

7472
/// <summary>
@@ -305,10 +303,6 @@ CancellationToken cancellationToken
305303
}
306304
}
307305

308-
#pragma warning disable IDE0230
309-
private static readonly ReadOnlyMemory<byte> EndOfLine = new[] { (byte)'\n' };
310-
#pragma warning restore IDE0230
311-
312306
private static readonly string[] ResponseLinesUnknownService = [
313307
"# Unknown service",
314308
".",
@@ -325,7 +319,7 @@ CancellationToken cancellationToken
325319
cancellationToken: cancellationToken
326320
);
327321

328-
protected async ValueTask SendResponseAsync(
322+
protected ValueTask SendResponseAsync(
329323
IMuninNodeClient client,
330324
IEnumerable<string> responseLines,
331325
CancellationToken cancellationToken
@@ -336,28 +330,65 @@ CancellationToken cancellationToken
336330
if (responseLines is null)
337331
throw new ArgumentNullException(nameof(responseLines));
338332

333+
var builder = responseBuilderPool.Take();
334+
335+
try {
336+
foreach (var line in responseLines) {
337+
builder.Append(line).Append('\n');
338+
}
339+
340+
return SendResponseAsync(client, builder, cancellationToken);
341+
}
342+
finally {
343+
responseBuilderPool.Return(builder);
344+
}
345+
}
346+
347+
private async ValueTask SendResponseAsync(
348+
IMuninNodeClient client,
349+
StringBuilder responseBuilder,
350+
CancellationToken cancellationToken
351+
)
352+
{
353+
if (client is null)
354+
throw new ArgumentNullException(nameof(client));
355+
if (responseBuilder is null)
356+
throw new ArgumentNullException(nameof(responseBuilder));
357+
339358
cancellationToken.ThrowIfCancellationRequested();
340359

341360
var writer = bufferWriterPool.Take();
342361

343362
try {
344-
foreach (var responseLine in responseLines) {
363+
#if SYSTEM_TEXT_STRINGBUILDER_GETCHUNKS
364+
foreach (var chunk in responseBuilder.GetChunks()) {
345365
#if SYSTEM_TEXT_ENCODINGEXTENSIONS
346-
_ = profile.Encoding.GetBytes(responseLine, writer);
366+
_ = profile.Encoding.GetBytes(chunk.Span, writer);
367+
#else
368+
var byteCount = profile.Encoding.GetByteCount(chunk);
369+
var buffer = writer.GetMemory(byteCount);
370+
var bytesWritten = profile.Encoding.GetBytes(chunk, buffer.Span);
347371

348-
writer.Write(EndOfLine.Span);
372+
writer.Advance(bytesWritten);
373+
#endif
374+
}
349375
#else
350-
var totalByteCount = profile.Encoding.GetByteCount(responseLine) + EndOfLine.Length;
351-
var buffer = writer.GetMemory(totalByteCount);
352-
var bytesWritten = profile.Encoding.GetBytes(responseLine, buffer.Span);
376+
var responseChars = ArrayPool<char>.Shared.Rent(responseBuilder.Length);
353377

354-
EndOfLine.CopyTo(buffer[bytesWritten..]);
378+
try {
379+
responseBuilder.CopyTo(0, responseChars, 0, responseBuilder.Length);
355380

356-
bytesWritten += EndOfLine.Length;
381+
var responseCharsMemory = responseChars.AsMemory(0, responseBuilder.Length);
382+
var byteCount = profile.Encoding.GetByteCount(responseCharsMemory.Span);
383+
var buffer = writer.GetMemory(byteCount);
384+
var bytesWritten = profile.Encoding.GetBytes(responseCharsMemory.Span, buffer.Span);
357385

358386
writer.Advance(bytesWritten);
359-
#endif
360387
}
388+
finally {
389+
ArrayPool<char>.Shared.Return(responseChars);
390+
}
391+
#endif
361392

362393
await client.SendAsync(
363394
buffer: writer.WrittenMemory,
@@ -526,45 +557,45 @@ await SendResponseAsync(
526557
return;
527558
}
528559

529-
var responseLines = responseLineListPool.Take();
560+
var responseBuilder = responseBuilderPool.Take();
530561

531562
try {
532563
if (plugin is IMultigraphPlugin multigraphPlugin) {
533564
// 'Protocol extension: multiple graphs from one plugin' (https://guide.munin-monitoring.org/en/latest/plugin/protocol-multigraph.html)
534565
foreach (var subPlugin in multigraphPlugin.Plugins) {
535-
responseLines.Add($"multigraph {subPlugin.Name}");
566+
responseBuilder.Append(provider: null, $"multigraph {subPlugin.Name}\n");
536567

537568
await WriteFetchResponseAsync(
538569
subPlugin.DataSource,
539-
responseLines,
570+
responseBuilder,
540571
cancellationToken
541572
).ConfigureAwait(false);
542573
}
543574
}
544575
else {
545576
await WriteFetchResponseAsync(
546577
plugin.DataSource,
547-
responseLines,
578+
responseBuilder,
548579
cancellationToken
549580
).ConfigureAwait(false);
550581
}
551582

552-
responseLines.Add(".");
583+
responseBuilder.Append(".\n");
553584

554585
await SendResponseAsync(
555586
client: client,
556-
responseLines: responseLines,
587+
responseBuilder: responseBuilder,
557588
cancellationToken: cancellationToken
558589
).ConfigureAwait(false);
559590
}
560591
finally {
561-
responseLineListPool.Return(responseLines);
592+
responseBuilderPool.Return(responseBuilder);
562593
}
563594
}
564595

565596
private static async ValueTask WriteFetchResponseAsync(
566597
IPluginDataSource dataSource,
567-
List<string> responseLines,
598+
StringBuilder responseBuilder,
568599
CancellationToken cancellationToken
569600
)
570601
{
@@ -573,7 +604,7 @@ CancellationToken cancellationToken
573604
cancellationToken: cancellationToken
574605
).ConfigureAwait(false);
575606

576-
responseLines.Add($"{field.Name}.value {valueString}");
607+
responseBuilder.Append(provider: null, $"{field.Name}.value {valueString}\n");
577608
}
578609
}
579610

@@ -606,18 +637,18 @@ CancellationToken cancellationToken
606637

607638
async ValueTask HandleConfigCommandAsyncCore()
608639
{
609-
var responseLines = responseLineListPool.Take();
640+
var responseBuilder = responseBuilderPool.Take();
610641

611642
try {
612643
if (plugin is IMultigraphPlugin multigraphPlugin) {
613644
// 'Protocol extension: multiple graphs from one plugin' (https://guide.munin-monitoring.org/en/latest/plugin/protocol-multigraph.html)
614645
foreach (var subPlugin in multigraphPlugin.Plugins) {
615-
responseLines.Add($"multigraph {subPlugin.Name}");
646+
responseBuilder.Append(provider: null, $"multigraph {subPlugin.Name}\n");
616647

617648
await WriteConfigResponseAsync(
618649
subPlugin,
619650
includeFetchResponse: IsDirtyConfigEnabled,
620-
responseLines,
651+
responseBuilder,
621652
cancellationToken
622653
).ConfigureAwait(false);
623654
}
@@ -626,40 +657,40 @@ await WriteConfigResponseAsync(
626657
await WriteConfigResponseAsync(
627658
plugin,
628659
includeFetchResponse: IsDirtyConfigEnabled,
629-
responseLines,
660+
responseBuilder,
630661
cancellationToken
631662
).ConfigureAwait(false);
632663
}
633664

634-
responseLines.Add(".");
665+
responseBuilder.Append(".\n");
635666

636667
await SendResponseAsync(
637668
client: client,
638-
responseLines: responseLines,
669+
responseBuilder: responseBuilder,
639670
cancellationToken: cancellationToken
640671
).ConfigureAwait(false);
641672
}
642673
finally {
643-
responseLineListPool.Return(responseLines);
674+
responseBuilderPool.Return(responseBuilder);
644675
}
645676
}
646677

647678
static ValueTask WriteConfigResponseAsync(
648679
IPlugin plugin,
649680
bool includeFetchResponse,
650-
List<string> responseLines,
681+
StringBuilder responseBuilder,
651682
CancellationToken cancellationToken
652683
)
653684
{
654685
WriteConfigResponse(
655686
plugin,
656-
responseLines
687+
responseBuilder
657688
);
658689

659690
if (includeFetchResponse) {
660691
return WriteFetchResponseAsync(
661692
dataSource: plugin.DataSource,
662-
responseLines: responseLines,
693+
responseBuilder: responseBuilder,
663694
cancellationToken: cancellationToken
664695
);
665696
}
@@ -670,28 +701,28 @@ CancellationToken cancellationToken
670701

671702
private static void WriteConfigResponse(
672703
IPlugin plugin,
673-
List<string> responseLines
704+
StringBuilder responseBuilder
674705
)
675706
{
676707
/*
677708
* global attributes
678709
*/
679-
responseLines.AddRange(
680-
plugin.GraphAttributes.EnumerateAttributes()
681-
);
710+
foreach (var graphAttribute in plugin.GraphAttributes.EnumerateAttributes()) {
711+
responseBuilder.Append(graphAttribute).Append('\n');
712+
}
682713

683714
/*
684715
* data source attributes
685716
*/
686717
WriteConfigDataSourceAttributes(
687718
plugin.DataSource,
688-
responseLines
719+
responseBuilder
689720
);
690721
}
691722

692723
private static void WriteConfigDataSourceAttributes(
693724
IPluginDataSource dataSource,
694-
List<string> responseLines
725+
StringBuilder responseBuilder
695726
)
696727
{
697728
var shouldHandleNegativeFields = dataSource
@@ -710,32 +741,32 @@ List<string> responseLines
710741
var fieldAttrs = field.Attributes;
711742
bool? graph = null;
712743

713-
responseLines.Add($"{field.Name}.label {fieldAttrs.Label}");
744+
responseBuilder.Append(provider: null, $"{field.Name}.label {fieldAttrs.Label}\n");
714745

715746
if (TranslateFieldDrawAttribute(fieldAttrs.GraphStyle) is string attrDraw)
716-
responseLines.Add($"{field.Name}.draw {attrDraw}");
747+
responseBuilder.Append(provider: null, $"{field.Name}.draw {attrDraw}\n");
717748

718749
if (FormatNormalValueRange(fieldAttrs.NormalRangeForWarning) is string attrWarning)
719-
responseLines.Add($"{field.Name}.warning {attrWarning}");
750+
responseBuilder.Append(provider: null, $"{field.Name}.warning {attrWarning}\n");
720751

721752
if (FormatNormalValueRange(fieldAttrs.NormalRangeForCritical) is string attrCritical)
722-
responseLines.Add($"{field.Name}.critical {attrCritical}");
753+
responseBuilder.Append(provider: null, $"{field.Name}.critical {attrCritical}\n");
723754

724755
if (shouldHandleNegativeFields && !string.IsNullOrEmpty(fieldAttrs.NegativeFieldName)) {
725756
var negativeField = dataSource.Fields.FirstOrDefault(
726757
f => string.Equals(fieldAttrs.NegativeFieldName, f.Name, StringComparison.Ordinal)
727758
);
728759

729760
if (negativeField is not null)
730-
responseLines.Add($"{field.Name}.negative {negativeField.Name}");
761+
responseBuilder.Append(provider: null, $"{field.Name}.negative {negativeField.Name}\n");
731762
}
732763

733764
// this field is defined as the negative field of other field, so should not be graphed
734765
if (shouldHandleNegativeFields && IsNegativeField(field, dataSource.Fields))
735766
graph = false;
736767

737768
if (graph is bool drawGraph)
738-
responseLines.Add($"{field.Name}.graph {(drawGraph ? "yes" : "no")}");
769+
responseBuilder.Append(provider: null, $"{field.Name}.graph {(drawGraph ? "yes" : "no")}\n");
739770
}
740771

741772
static bool IsNegativeField(IPluginField field, IReadOnlyCollection<IPluginField> fields)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// SPDX-FileCopyrightText: 2021 smdn <smdn@smdn.jp>
2+
// SPDX-License-Identifier: MIT
3+
#if !SYSTEM_TEXT_STRINGBUILDER_APPEND_IFORMATPROVIDER_APPENDINTERPOLATEDSTRINGHANDLER
4+
using System;
5+
using System.Text;
6+
7+
namespace Smdn.Net.MuninNode.Protocol;
8+
9+
internal static class StringBuilderExtensions {
10+
#pragma warning disable IDE0060
11+
public static StringBuilder Append(this StringBuilder builder, IFormatProvider? provider, string value)
12+
=> builder.Append(value);
13+
#pragma warning restore IDE0060
14+
}
15+
#endif

0 commit comments

Comments
 (0)