From 5751d90b48386fe6ec4ba0784945166ad9308755 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:43:07 +0200 Subject: [PATCH 01/13] wip - add tests --- eng/MSBuild/Shared.props | 4 + .../ITcpTableInfo.cs | 22 + .../Linux/IFileSystem.cs | 14 +- .../Linux/Network/LinuxNetworkMetrics.cs | 81 ++ .../Network/LinuxNetworkUtilizationParser.cs | 147 +++ .../Linux/Network/LinuxTcpState.cs | 43 + .../Linux/Network/LinuxTcpTableInfo.cs | 51 + .../Linux/OSFileSystem.cs | 51 + ...ions.Diagnostics.ResourceMonitoring.csproj | 5 +- ...ceMonitoringServiceCollectionExtensions.cs | 10 +- .../ResourceUtilizationInstruments.cs | 8 + .../{Windows/Network => }/TcpStateInfo.cs | 2 +- .../{ => Network}/WindowsNetworkMetrics.cs | 16 +- ...TcpTableInfo.cs => WindowsTcpTableInfo.cs} | 8 +- src/Shared/BufferWriterPool/BufferWriter.cs | 2 - .../BufferWriterPooledObjectPolicy.cs | 2 - .../Data.Validation/TimeSpanAttribute.cs | 2 - src/Shared/Debugger/DebuggerExtensions.cs | 2 - src/Shared/RentedSpan/RentedSpan.cs | 2 - src/Shared/StringSplit/README.md | 11 + src/Shared/StringSplit/StringRange.cs | 164 ++++ .../StringSplit/StringSplitExtensions.cs | 891 ++++++++++++++++++ .../Resources/FileNamesOnlyFileSystem.cs | 5 + .../Resources/HardcodedValueFileSystem.cs | 5 + ...ceMonitoringOptionsCustomValidatorTests.cs | 2 +- .../Windows/Tcp6TableInfoTests.cs | 16 +- .../Windows/TcpTableInfoTests.cs | 18 +- .../Windows/WindowsCountersTests.cs | 4 +- .../StringSplit/SplitExtensionsTests.cs | 426 +++++++++ test/Shared/StringSplit/StringRangeTests.cs | 42 + 30 files changed, 2006 insertions(+), 50 deletions(-) create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpState.cs create mode 100644 src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs rename src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/{Windows/Network => }/TcpStateInfo.cs (95%) rename src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/{ => Network}/WindowsNetworkMetrics.cs (91%) rename src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/{TcpTableInfo.cs => WindowsTcpTableInfo.cs} (97%) create mode 100644 src/Shared/StringSplit/README.md create mode 100644 src/Shared/StringSplit/StringRange.cs create mode 100644 src/Shared/StringSplit/StringSplitExtensions.cs create mode 100644 test/Shared/StringSplit/SplitExtensionsTests.cs create mode 100644 test/Shared/StringSplit/StringRangeTests.cs diff --git a/eng/MSBuild/Shared.props b/eng/MSBuild/Shared.props index 2662152ff85..14fa8b868cd 100644 --- a/eng/MSBuild/Shared.props +++ b/eng/MSBuild/Shared.props @@ -38,4 +38,8 @@ + + + + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs new file mode 100644 index 00000000000..90ec8c5664f --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; + +/// +/// An interface for getting TCP/IP table information. +/// +internal interface ITcpTableInfo +{ + /// + /// Gets the last known snapshot of TCP/IP v4 state info on the system. + /// + /// An instance of . + TcpStateInfo GetIpV4CachingSnapshot(); + + /// + /// Gets the last known snapshot of TCP/IP v6 state info on the system. + /// + /// An instance of . + TcpStateInfo GetIpV6CachingSnapshot(); +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs index bce4929dfe0..2cb34d777ca 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs @@ -16,20 +16,20 @@ internal interface IFileSystem /// /// Checks for file existence. /// - /// True/False. + /// or . bool Exists(FileInfo fileInfo); /// /// Get directory names on the filesystem based on the provided pattern. /// - /// string. + /// IReadOnlyCollection. IReadOnlyCollection GetDirectoryNames(string directory, string pattern); /// /// Reads content from the file. /// /// - /// Chars written. + /// Number of chars written. /// int Read(FileInfo file, int length, Span destination); @@ -42,4 +42,12 @@ internal interface IFileSystem /// Reads first line from the file. /// void ReadFirstLine(FileInfo file, BufferWriter destination); + + /// + /// Reads all content from a file by line. + /// + /// + /// IEnumerable. + /// + IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs new file mode 100644 index 00000000000..07c71fa039c --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Metrics; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; + +internal sealed class LinuxNetworkMetrics +{ + private readonly ITcpTableInfo _tcpTableInfo; + + public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpTableInfo tcpTableInfo) + { + _tcpTableInfo = tcpTableInfo; + +#pragma warning disable CA2000 // Dispose objects before losing scope + // We don't dispose the meter because IMeterFactory handles that + // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 + // Related documentation: https://github.com/dotnet/docs/pull/37170 + var meter = meterFactory.Create(nameof(ResourceMonitoring)); +#pragma warning restore CA2000 // Dispose objects before losing scope + + var tcpTag = new KeyValuePair("network.transport", "tcp"); + var commonTags = new TagList + { + tcpTag + }; + + // The metric is aligned with + // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemnetworkconnections + _ = meter.CreateObservableUpDownCounter( + ResourceUtilizationInstruments.SystemNetworkConnections, + GetMeasurements, + unit: "{connection}", + description: null, + tags: commonTags); + } + + private IEnumerable> GetMeasurements() + { + const string NetworkStateKey = "system.network.state"; + + // These are covered in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes: + var tcpVersionFourTag = new KeyValuePair("network.type", "ipv4"); + var tcpVersionSixTag = new KeyValuePair("network.type", "ipv6"); + + var measurements = new List>(24); + + // IPv4: + TcpStateInfo snapshotV4 = _tcpTableInfo.GetIpV4CachingSnapshot(); + measurements.Add(new Measurement(snapshotV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(snapshotV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(snapshotV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(snapshotV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(snapshotV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(snapshotV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(snapshotV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(snapshotV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(snapshotV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(snapshotV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(snapshotV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); + + // IPv6: + TcpStateInfo snapshotV6 = _tcpTableInfo.GetIpV6CachingSnapshot(); + measurements.Add(new Measurement(snapshotV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(snapshotV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(snapshotV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(snapshotV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(snapshotV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(snapshotV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(snapshotV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(snapshotV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(snapshotV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(snapshotV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(snapshotV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); + + return measurements; + } +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs new file mode 100644 index 00000000000..48f5ee90c29 --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -0,0 +1,147 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Microsoft.Extensions.ObjectPool; +#if !NET8_0_OR_GREATER +using Microsoft.Shared.StringSplit; +#endif +using Microsoft.Shared.Diagnostics; +using Microsoft.Shared.Pools; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; + +internal class LinuxNetworkUtilizationParser +{ + private static readonly ObjectPool> _sharedBufferWriterPool = BufferWriterPool.CreateBufferWriterPool(); + + /// + /// File that provide information about currently active TCP_IPv4 connections. + /// + private static readonly FileInfo _tcp = new("/proc/net/tcp"); + + /// + /// File that provide information about currently active TCP_IPv6 connections. + /// + private static readonly FileInfo _tcp6 = new("/proc/net/tcp6"); + + private readonly IFileSystem _fileSystem; + + /// + /// Reads the contents of a file located at _tcp4 and parses it to extract information about the TCP/IP state info on the system. + /// + public TcpStateInfo GetTcpIPv4StateInfo() => GetTcpStateInfo(_tcp); + + /// + /// Reads the contents of a file located at _tcp6 and parses it to extract information about the TCP/IP state info on the system. + /// + public TcpStateInfo GetTcpIPv6StateInfo() => GetTcpStateInfo(_tcp6); + + public LinuxNetworkUtilizationParser(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + } + + /// + /// The method is used in the LinuxUtilizationParser class to read Span data and calculate the TCP state info. + /// Refer proc net tcp. + /// + [SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", + Justification = "We are adding another digit, so we need to multiply by ten.")] + private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo tcpStateInfo) + { + ReadOnlySpan line = buffer.TrimStart(); + const int Target = 4; + +#if NET8_0_OR_GREATER + Span range = stackalloc Range[Target]; + int numRanges = line.Split(range, ' '); +#else + Span range = stackalloc StringRange[Target]; + _ = line.TrySplit(" ", range, out int numRanges, StringComparison.OrdinalIgnoreCase, StringSplitOptions.RemoveEmptyEntries); +#endif + if (numRanges < Target) + { + Throw.InvalidOperationException($"Could not split contents. We expected every line to contain more than {Target - 1} elements, but it has only {numRanges} elements."); + } + +#if NET8_0_OR_GREATER + ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 1].Start.Value, range[Target - 1].End.Value - range[Target - 1].Start.Value); +#else + ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 1].Index, range[Target - 1].Count); +#endif + + // until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 + // we have to allocate & throw away memory using ToString(): + switch ((LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), 16)) + { + case LinuxTcpState.ESTABLISHED: + tcpStateInfo.EstabCount++; + break; + case LinuxTcpState.SYN_SENT: + tcpStateInfo.SynSentCount++; + break; + case LinuxTcpState.SYN_RECV: + tcpStateInfo.SynRcvdCount++; + break; + case LinuxTcpState.FIN_WAIT1: + tcpStateInfo.FinWait1Count++; + break; + case LinuxTcpState.FIN_WAIT2: + tcpStateInfo.FinWait2Count++; + break; + case LinuxTcpState.TIME_WAIT: + tcpStateInfo.TimeWaitCount++; + break; + case LinuxTcpState.CLOSE: + tcpStateInfo.ClosedCount++; + break; + case LinuxTcpState.CLOSE_WAIT: + tcpStateInfo.CloseWaitCount++; + break; + case LinuxTcpState.LAST_ACK: + tcpStateInfo.LastAckCount++; + break; + case LinuxTcpState.LISTEN: + tcpStateInfo.ListenCount++; + break; + case LinuxTcpState.CLOSING: + tcpStateInfo.ClosingCount++; + break; + default: + throw new InvalidEnumArgumentException($"Cannot find status: {tcpConnectionState} in LinuxTcpState"); + } + } + + /// + /// Reads the contents of a file located at file and parses it to extract information about the TCP/IP state info on the system. + /// + private TcpStateInfo GetTcpStateInfo(FileInfo file) + { + // The value we are interested in starts with this. We just want to make sure it is true. + const string Sl = "sl"; + var tcpStateInfo = new TcpStateInfo(); + using ReturnableBufferWriter bufferWriter = new(_sharedBufferWriterPool); + using var enumerableLines = _fileSystem.ReadAllByLines(file, bufferWriter.Buffer).GetEnumerator(); + if (!enumerableLines.MoveNext()) + { + Throw.InvalidOperationException($"Could not parse '{file}'. File was empty."); + } + + var firstLine = enumerableLines.Current.TrimStart().Span; + if (!firstLine.StartsWith(Sl, StringComparison.Ordinal)) + { + Throw.InvalidOperationException($"Could not parse '{file}'. We expected first line of the file to start with '{Sl}' but it was '{firstLine.ToString()}' instead."); + } + + while (enumerableLines.MoveNext()) + { + UpdateTcpStateInfo(enumerableLines.Current.Span, tcpStateInfo); + } + + return tcpStateInfo; + } +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpState.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpState.cs new file mode 100644 index 00000000000..3f9ea5acc1d --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpState.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; + +/// +/// Enumerates all possible TCP states on Linux. +/// +internal enum LinuxTcpState +{ + /// The TCP connection was established. + ESTABLISHED = 1, + + /// The TCP connection has sent a SYN packet. + SYN_SENT = 2, + + /// The TCP connection has received a SYN packet. + SYN_RECV = 3, + + /// The TCP connection is waiting for a FIN packet. + FIN_WAIT1 = 4, + + /// The TCP connection is waiting for a FIN packet. + FIN_WAIT2 = 5, + + /// The TCP connection is in the time wait state. + TIME_WAIT = 6, + + /// The TCP connection is closed. + CLOSE = 7, + + /// The TCP connection is in the close wait state. + CLOSE_WAIT = 8, + + /// The TCP connection is in the last ACK state. + LAST_ACK = 9, + + /// The TCP connection is in the listen state. + LISTEN = 10, + + /// The TCP connection is closing. + CLOSING = 11 +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs new file mode 100644 index 00000000000..69b796b1e7a --- /dev/null +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; + +internal class LinuxTcpTableInfo : ITcpTableInfo +{ + private readonly object _lock = new(); + private readonly TimeSpan _samplingInterval; + private readonly LinuxNetworkUtilizationParser _parser; + private readonly TimeProvider _timeProvider; + + private TcpStateInfo _iPv4Snapshot = new(); + private TcpStateInfo _iPv6Snapshot = new(); + private DateTimeOffset _refreshAfter; + + public LinuxTcpTableInfo(IOptions options, LinuxNetworkUtilizationParser parser, TimeProvider timeProvider) + { + _samplingInterval = options.Value.SamplingInterval; + _parser = parser; + _timeProvider = timeProvider; + } + + public TcpStateInfo GetIpV4CachingSnapshot() + { + RefreshSnapshotIfNeeded(); + return _iPv4Snapshot; + } + + public TcpStateInfo GetIpV6CachingSnapshot() + { + RefreshSnapshotIfNeeded(); + return _iPv6Snapshot; + } + + private void RefreshSnapshotIfNeeded() + { + lock (_lock) + { + if (_refreshAfter < _timeProvider.GetUtcNow()) + { + _iPv4Snapshot = _parser.GetTcpIPv4StateInfo(); + _iPv6Snapshot = _parser.GetTcpIPv6StateInfo(); + _refreshAfter = _timeProvider.GetUtcNow().Add(_samplingInterval); + } + } + } +} diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs index b49f6d77df5..010dea4c9f0 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using Microsoft.Shared.Pools; @@ -45,6 +47,55 @@ public void ReadFirstLine(FileInfo file, BufferWriter destination) public void ReadAll(FileInfo file, BufferWriter destination) => ReadUntilTerminatorOrEnd(file, destination, null); + public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) + { + const int MaxStackalloc = 256; + + if (!file.Exists) + { + throw new FileNotFoundException(); + } + + using FileStream stream = file.OpenRead(); + + Memory buffer = ArrayPool.Shared.Rent(MaxStackalloc); + int read = stream.Read(buffer.Span); + while (read > 0) + { + var start = 0; + var end = 0; + + for (end = 0; end < read; end++) + { + if (buffer.Span[end] == (byte)'\n') + { + var length = end - start; + _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length)); + destination.Advance(length); + start = end + 1; + yield return destination.WrittenMemory; + destination.Reset(); + } + } + + // Set the comparison in the while loop to end when the file has not been completely read into the buffer. + // It will then advance the last character to the destination for the next time yield return is called. + if (start < read) + { + var length = read - start; + _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length)); + destination.Advance(length); + } + + read = stream.Read(buffer.Span); + } + + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment arraySegment) && arraySegment.Array != null) + { + ArrayPool.Shared.Return(arraySegment.Array); + } + } + [SkipLocalsInit] private static void ReadUntilTerminatorOrEnd(FileInfo file, BufferWriter destination, byte? terminator) { diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj index beffa2e9ac8..a98c18191be 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj @@ -1,4 +1,4 @@ - + Microsoft.Extensions.Diagnostics.ResourceMonitoring Measures processor and memory usage. @@ -14,6 +14,7 @@ true true true + true @@ -23,7 +24,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs index f370d3bb289..83db13a3cd4 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs @@ -7,6 +7,8 @@ using Microsoft.Extensions.Diagnostics.ResourceMonitoring; #if !NETFRAMEWORK using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; + #endif using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Interop; @@ -91,7 +93,7 @@ private static ResourceMonitorBuilder AddWindowsProvider(this ResourceMonitorBui .AddActivatedSingleton(); _ = builder.Services - .AddActivatedSingleton(); + .AddActivatedSingleton(); return builder; } @@ -120,6 +122,12 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild builder.Services.TryAddSingleton(); builder.PickLinuxParser(); + _ = builder.Services + .AddActivatedSingleton(); + + _ = builder.Services + .AddActivatedSingleton(); + return builder; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs index aa3fd012029..1669967f41a 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceUtilizationInstruments.cs @@ -53,4 +53,12 @@ internal static class ResourceUtilizationInstruments /// The type of an instrument is . /// public const string ProcessMemoryUtilization = "dotnet.process.memory.virtual.utilization"; + + /// + /// The name of an instrument to retrieve network connections information. + /// + /// + /// The type of an instrument is . + /// + public const string SystemNetworkConnections = "system.network.connections"; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/TcpStateInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/TcpStateInfo.cs similarity index 95% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/TcpStateInfo.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/TcpStateInfo.cs index 245ebfd5e37..83dbe92af86 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/TcpStateInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/TcpStateInfo.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; /// /// TcpStateInfo contains different possible TCP state counts. diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs similarity index 91% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsNetworkMetrics.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs index 115f7c95962..810eba9c2a2 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/WindowsNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs @@ -4,15 +4,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Metrics; -using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; -namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows; +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; internal sealed class WindowsNetworkMetrics { - private readonly TcpTableInfo _tcpTableInfo; + private readonly ITcpTableInfo _tcpTableInfo; - public WindowsNetworkMetrics(IMeterFactory meterFactory, TcpTableInfo tcpTableInfo) + public WindowsNetworkMetrics(IMeterFactory meterFactory, ITcpTableInfo tcpTableInfo) { _tcpTableInfo = tcpTableInfo; @@ -20,7 +19,7 @@ public WindowsNetworkMetrics(IMeterFactory meterFactory, TcpTableInfo tcpTableIn // We don't dispose the meter because IMeterFactory handles that // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 // Related documentation: https://github.com/dotnet/docs/pull/37170 - var meter = meterFactory.Create(nameof(Microsoft.Extensions.Diagnostics.ResourceMonitoring)); + var meter = meterFactory.Create(nameof(ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope var tcpTag = new KeyValuePair("network.transport", "tcp"); @@ -31,9 +30,8 @@ public WindowsNetworkMetrics(IMeterFactory meterFactory, TcpTableInfo tcpTableIn // The metric is aligned with // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemnetworkconnections - _ = meter.CreateObservableUpDownCounter( - "system.network.connections", + ResourceUtilizationInstruments.SystemNetworkConnections, GetMeasurements, unit: "{connection}", description: null, @@ -51,7 +49,7 @@ private IEnumerable> GetMeasurements() var measurements = new List>(24); // IPv4: - var snapshotV4 = _tcpTableInfo.GetIPv4CachingSnapshot(); + var snapshotV4 = _tcpTableInfo.GetIpV4CachingSnapshot(); measurements.Add(new Measurement(snapshotV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); measurements.Add(new Measurement(snapshotV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); measurements.Add(new Measurement(snapshotV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); @@ -66,7 +64,7 @@ private IEnumerable> GetMeasurements() measurements.Add(new Measurement(snapshotV4.DeleteTcbCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "delete") })); // IPv6: - var snapshotV6 = _tcpTableInfo.GetIPv6CachingSnapshot(); + var snapshotV6 = _tcpTableInfo.GetIpV6CachingSnapshot(); measurements.Add(new Measurement(snapshotV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); measurements.Add(new Measurement(snapshotV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); measurements.Add(new Measurement(snapshotV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/TcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpTableInfo.cs similarity index 97% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/TcpTableInfo.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpTableInfo.cs index ef8f7ed617b..e203f5f0828 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/TcpTableInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpTableInfo.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; -internal sealed class TcpTableInfo +internal sealed class WindowsTcpTableInfo : ITcpTableInfo { private readonly object _lock = new(); private readonly FrozenSet _localIPAddresses; @@ -25,7 +25,7 @@ internal sealed class TcpTableInfo private static TimeProvider TimeProvider => TimeProvider.System; private DateTimeOffset _refreshAfter; - public TcpTableInfo(IOptions options) + public WindowsTcpTableInfo(IOptions options) { var stringAddresses = options.Value.SourceIpAddresses; _localIPAddresses = stringAddresses @@ -42,13 +42,13 @@ public TcpTableInfo(IOptions options) _refreshAfter = default; } - public TcpStateInfo GetIPv4CachingSnapshot() + public TcpStateInfo GetIpV4CachingSnapshot() { RefreshSnapshotIfNeeded(); return _iPv4Snapshot; } - public TcpStateInfo GetIPv6CachingSnapshot() + public TcpStateInfo GetIpV6CachingSnapshot() { RefreshSnapshotIfNeeded(); return _iPv6Snapshot; diff --git a/src/Shared/BufferWriterPool/BufferWriter.cs b/src/Shared/BufferWriterPool/BufferWriter.cs index 18269616f49..f8dc2b7c42a 100644 --- a/src/Shared/BufferWriterPool/BufferWriter.cs +++ b/src/Shared/BufferWriterPool/BufferWriter.cs @@ -8,9 +8,7 @@ using System.Runtime.CompilerServices; #endif -#pragma warning disable CA1716 namespace Microsoft.Shared.Pools; -#pragma warning restore CA1716 /// /// Represents an output sink into which data can be written. diff --git a/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs b/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs index 50533f74b2b..d11d41a6930 100644 --- a/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs +++ b/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs @@ -4,9 +4,7 @@ using Microsoft.Extensions.ObjectPool; using Microsoft.Shared.Diagnostics; -#pragma warning disable CA1716 namespace Microsoft.Shared.Pools; -#pragma warning restore CA1716 /// /// An object pooling policy designed for . diff --git a/src/Shared/Data.Validation/TimeSpanAttribute.cs b/src/Shared/Data.Validation/TimeSpanAttribute.cs index 8830a4120e5..bc181e645cf 100644 --- a/src/Shared/Data.Validation/TimeSpanAttribute.cs +++ b/src/Shared/Data.Validation/TimeSpanAttribute.cs @@ -7,9 +7,7 @@ using System.Globalization; using Microsoft.Shared.Diagnostics; -#pragma warning disable CA1716 namespace Microsoft.Shared.Data.Validation; -#pragma warning restore CA1716 /// /// Provides boundary validation for . diff --git a/src/Shared/Debugger/DebuggerExtensions.cs b/src/Shared/Debugger/DebuggerExtensions.cs index cc197be12c2..4aa63ee590a 100644 --- a/src/Shared/Debugger/DebuggerExtensions.cs +++ b/src/Shared/Debugger/DebuggerExtensions.cs @@ -5,9 +5,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Throw = Microsoft.Shared.Diagnostics.Throw; -#pragma warning disable CA1716 namespace Microsoft.Shared.Diagnostics; -#pragma warning restore CA1716 /// /// Adds debugger to DI container. diff --git a/src/Shared/RentedSpan/RentedSpan.cs b/src/Shared/RentedSpan/RentedSpan.cs index c7b429b0b67..efebc2e2ffe 100644 --- a/src/Shared/RentedSpan/RentedSpan.cs +++ b/src/Shared/RentedSpan/RentedSpan.cs @@ -5,9 +5,7 @@ using System.Buffers; using System.Runtime.CompilerServices; -#pragma warning disable CA1716 namespace Microsoft.Shared.Pools; -#pragma warning restore CA1716 /// /// Represents a span that's potentially created over a rented array. diff --git a/src/Shared/StringSplit/README.md b/src/Shared/StringSplit/README.md new file mode 100644 index 00000000000..9c9f1110e7e --- /dev/null +++ b/src/Shared/StringSplit/README.md @@ -0,0 +1,11 @@ +# Numeric Extensions + +`StringSplit` API to get allocation free string splitting for .NET runtime before .NET 8 + +To use this in your project, add the following to your `.csproj` file: + +```xml + + true + +``` diff --git a/src/Shared/StringSplit/StringRange.cs b/src/Shared/StringSplit/StringRange.cs new file mode 100644 index 00000000000..2acd9317f77 --- /dev/null +++ b/src/Shared/StringSplit/StringRange.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET8_0_OR_GREATER + +using System; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Shared.StringSplit; + +#if !SHARED_PROJECT +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif +internal readonly struct StringRange : IComparable, IComparable, IEquatable +{ + /// + /// Initializes a new instance of the struct. + /// + /// Starting index of the segment. + /// Number of characters in the segment. + public StringRange(int index, int count) + { + _ = Throw.IfLessThan(index, 0); + _ = Throw.IfLessThan(count, 0); + + Index = index; + Count = count; + } + + /// + /// Gets the starting index of the string. + /// + public int Index { get; } + + /// + /// Gets the number of characters in the segment. + /// + public int Count { get; } + + /// + /// Compare current instance of to another. + /// + /// Segment to compare. + /// + /// Returns a value less than zero if this less than other, zero if this equal to other, + /// or a value greater than zero if this greater than other. + /// + public int CompareTo(StringRange other) => Index.CompareTo(other.Index); + + /// + /// Compare current instance of to another object. + /// + /// Segment to compare. + /// + /// Returns a value less than zero if this less than other, zero if this equal to other, + /// or a value greater than zero if this greater than other. + /// Null is considered to be less than any instance. + /// + /// + /// If object is not of same type. + /// + public int CompareTo(object? obj) + { + if (obj is StringRange ss) + { + return CompareTo(ss); + } + + if (obj != null) + { + Throw.ArgumentException(nameof(obj), $"Provided value must be of type {typeof(StringRange)}, but was of type {obj.GetType()}."); + } + + return 1; + } + + /// + /// Compares two string segments. + /// + /// Segment to compare. + /// when equal, otherwise. + public bool Equals(StringRange other) => other.Index == Index && other.Count == Count; + + /// + /// Compares two string segments. + /// + /// Segment to compare. + /// when equal, otherwise. + public override bool Equals(object? obj) => obj is StringRange ss && Equals(ss); + + /// + /// Returns the hashcode for this instance. + /// + /// Hash code. + public override int GetHashCode() => HashCode.Combine(Index, Count); + + /// + /// Compares two string segments. + /// + /// Left segment to compare. + /// Right segment to compare. + /// when equal, otherwise. + public static bool operator ==(StringRange left, StringRange right) + { + return left.Equals(right); + } + + /// + /// Compares two string segments. + /// + /// Left segment to compare. + /// Right segment to compare. + /// when not equal, otherwise. + public static bool operator !=(StringRange left, StringRange right) + { + return !(left == right); + } + + /// + /// Compares two string segments. + /// + /// Left segment to compare. + /// Right segment to compare. + /// when first segment is before the second, otherwise. + public static bool operator <(StringRange left, StringRange right) + { + return left.Index < right.Index; + } + + /// + /// Compares two string segments. + /// + /// Left segment to compare. + /// Right segment to compare. + /// when first segment is after the second, otherwise. + public static bool operator >(StringRange left, StringRange right) + { + return left.Index > right.Index; + } + + /// + /// Compares two string segments. + /// + /// Left segment to compare. + /// Right segment to compare. + /// when first segment is before or at the same index as the second, otherwise. + public static bool operator <=(StringRange left, StringRange right) + { + return left.Index <= right.Index; + } + + /// + /// Compares two string segments. + /// + /// Left segment to compare. + /// Right segment to compare. + /// when first segment is at the same index or after the second, otherwise. + public static bool operator >=(StringRange left, StringRange right) + { + return left.Index >= right.Index; + } +} + +#endif diff --git a/src/Shared/StringSplit/StringSplitExtensions.cs b/src/Shared/StringSplit/StringSplitExtensions.cs new file mode 100644 index 00000000000..cbc8aedc805 --- /dev/null +++ b/src/Shared/StringSplit/StringSplitExtensions.cs @@ -0,0 +1,891 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET8_0_OR_GREATER + +using System; +using Microsoft.Shared.Diagnostics; + +namespace Microsoft.Shared.StringSplit; + +#if !SHARED_PROJECT +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif +internal static class StringSplitExtensions +{ + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// A character that delimits the substrings in this instance. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this ReadOnlySpan input, + char separator, + Span result, + out int numSegments, + StringSplitOptions options = StringSplitOptions.None) + { + const int SeparatorLen = 1; + + CheckStringSplitOptions(options); + + numSegments = 0; + + int start = 0; + while (true) + { + int index = input.Slice(start).IndexOf(separator); + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + if (numSegments >= result.Length) + { + return false; + } + + result[numSegments++] = new StringRange(rangeStart, sp.Length); + } + + if (index < 0) + { + return true; + } + + start += index + SeparatorLen; + } + } + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// The characters that delimit the substrings in this instance. This is not treated as a string, this is used as an array of individual characters. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this ReadOnlySpan input, + ReadOnlySpan separators, + Span result, + out int numSegments, + StringSplitOptions options = StringSplitOptions.None) + { + const int SeparatorLen = 1; + + CheckStringSplitOptions(options); + + numSegments = 0; + + int start = 0; + while (true) + { + int index = input.Slice(start).IndexOfAny(separators); + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + if (numSegments >= result.Length) + { + return false; + } + + result[numSegments++] = new StringRange(rangeStart, sp.Length); + } + + if (index < 0) + { + return true; + } + + start += index + SeparatorLen; + } + } + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// The strings that delimit the substrings in this instance. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this ReadOnlySpan input, + string[] separators, + Span result, + out int numSegments, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + { + _ = Throw.IfNull(separators); + CheckStringSplitOptions(options); + + numSegments = 0; + + int start = 0; + while (true) + { + int index = -1; + int separatorLen = 0; + foreach (var sep in separators) + { + int found = input.Slice(start).IndexOf(sep.AsSpan(), comparison); + if (found >= 0) + { + if (found < index || index < 0) + { + separatorLen = sep.Length; + index = found; + } + } + } + + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + if (numSegments >= result.Length) + { + return false; + } + + result[numSegments++] = new StringRange(rangeStart, sp.Length); + } + + if (index < 0) + { + return true; + } + + start += index + separatorLen; + } + } + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// The string that delimits the substrings in this instance. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this ReadOnlySpan input, + string separator, + Span result, + out int numSegments, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + { + _ = Throw.IfNull(separator); + CheckStringSplitOptions(options); + + numSegments = 0; + + int start = 0; + while (true) + { + int index = -1; + int separatorLen = 0; + + int found = input.Slice(start).IndexOf(separator.AsSpan(), comparison); + if (found >= 0) + { + if (found < index || index < 0) + { + separatorLen = separator.Length; + index = found; + } + } + + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + if (numSegments >= result.Length) + { + return false; + } + + result[numSegments++] = new StringRange(rangeStart, sp.Length); + } + + if (index < 0) + { + return true; + } + + start += index + separatorLen; + } + } + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + /// This uses whitespace as a separator of individual substrings. + public static bool TrySplit( + this ReadOnlySpan input, + Span result, + out int numSegments, + StringSplitOptions options = StringSplitOptions.None) + { + const int SeparatorLen = 1; + + CheckStringSplitOptions(options); + + numSegments = 0; + + int start = 0; + while (true) + { + int index = -1; + for (int i = start; i < input.Length; i++) + { + if (char.IsWhiteSpace(input[i])) + { + index = i - start; + break; + } + } + + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + if (numSegments >= result.Length) + { + return false; + } + + result[numSegments++] = new StringRange(rangeStart, sp.Length); + } + + if (index < 0) + { + return true; + } + + start += index + SeparatorLen; + } + } + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// A character that delimits the substrings in this instance. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this string input, + char separator, + Span result, + out int numSegments, + StringSplitOptions options = StringSplitOptions.None) + => TrySplit(input.AsSpan(), separator, result, out numSegments, options); + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// The characters that delimit the substrings in this instance. This is not treated as a string, this is used as an array of individual characters. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this string input, + ReadOnlySpan separators, + Span result, + out int numSegments, + StringSplitOptions options = StringSplitOptions.None) + => TrySplit(input.AsSpan(), separators, result, out numSegments, options); + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// The strings that delimit the substrings in this instance. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this string input, + string[] separators, + Span result, + out int numSegments, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + => TrySplit(input.AsSpan(), separators, result, out numSegments, comparison, options); + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// The string that delimits the substrings in this instance. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + public static bool TrySplit( + this string input, + string separator, + Span result, + out int numSegments, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + => TrySplit(input.AsSpan(), separator, result, out numSegments, comparison, options); + + /// + /// Splits a string into a number of string segments. + /// + /// The string to split. + /// A span to receive the individual string segments. + /// The number of string segments copied to the output. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// if there was enough space in the output array, otherwise . + /// This uses whitespace as a separator of individual substrings. + public static bool TrySplit( + this string input, + Span result, + out int numSegments, + StringSplitOptions options = StringSplitOptions.None) => TrySplit(input.AsSpan(), result, out numSegments, options); + + private static void CheckStringSplitOptions(StringSplitOptions options) + { +#if NET5_0_OR_GREATER + const StringSplitOptions AllValidFlags = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; +#else + const StringSplitOptions AllValidFlags = StringSplitOptions.RemoveEmptyEntries; +#endif + + if ((options & ~AllValidFlags) != 0) + { + // at least one invalid flag was set + Throw.ArgumentException(nameof(options), "Invalid split options specified"); + } + } + + /// + /// The delegate that gets invoked when visiting the splits of a string. + /// + /// Type of the context value given to the delegate. + /// The span of characters that makes up the split. + /// The monotonically increasing split count. + /// The user-defined context object. + public delegate void SplitVisitor(ReadOnlySpan split, int segmentNum, T context); + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// A character that delimits the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this ReadOnlySpan input, + char separator, + TContext context, + SplitVisitor visitor, + StringSplitOptions options = StringSplitOptions.None) + { + const int SeparatorLen = 1; + + _ = Throw.IfNull(visitor); + CheckStringSplitOptions(options); + + int numSegments = 0; + int start = 0; + while (true) + { + int index = input.Slice(start).IndexOf(separator); + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); + } + + if (index < 0) + { + return; + } + + start += index + SeparatorLen; + } + } + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// The characters that delimit the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this ReadOnlySpan input, + ReadOnlySpan separators, + TContext context, + SplitVisitor visitor, + StringSplitOptions options = StringSplitOptions.None) + { + const int SeparatorLen = 1; + + _ = Throw.IfNull(visitor); + CheckStringSplitOptions(options); + + int numSegments = 0; + int start = 0; + while (true) + { + int index = input.Slice(start).IndexOfAny(separators); + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); + } + + if (index < 0) + { + return; + } + + start += index + SeparatorLen; + } + } + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// The strings that delimit the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this ReadOnlySpan input, + string[] separators, + TContext context, + SplitVisitor visitor, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + { + _ = Throw.IfNull(separators); + _ = Throw.IfNull(visitor); + CheckStringSplitOptions(options); + + int numSegments = 0; + int start = 0; + while (true) + { + int index = -1; + int separatorLen = 0; + foreach (var sep in separators) + { + int found = input.Slice(start).IndexOf(sep.AsSpan(), comparison); + if (found >= 0) + { + if (found < index || index < 0) + { + separatorLen = sep.Length; + index = found; + } + } + } + + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); + } + + if (index < 0) + { + return; + } + + start += index + separatorLen; + } + } + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// The string that delimits the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static void VisitSplits( + this ReadOnlySpan input, + string separator, + TContext context, + SplitVisitor visitor, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + { + _ = Throw.IfNull(separator); + _ = Throw.IfNull(visitor); + CheckStringSplitOptions(options); + + int numSegments = 0; + int start = 0; + while (true) + { + int index = -1; + int separatorLen = 0; + + int found = input.Slice(start).IndexOf(separator.AsSpan(), comparison); + if (found >= 0) + { + if (found < index || index < 0) + { + separatorLen = separator.Length; + index = found; + } + } + + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); + } + + if (index < 0) + { + return; + } + + start += index + separatorLen; + } + } + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// This uses whitespace as a separator of individual substrings. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this ReadOnlySpan input, + TContext context, + SplitVisitor visitor, + StringSplitOptions options = StringSplitOptions.None) + { + const int SeparatorLen = 1; + + _ = Throw.IfNull(visitor); + CheckStringSplitOptions(options); + + int numSegments = 0; + int start = 0; + while (true) + { + int index = -1; + for (int i = start; i < input.Length; i++) + { + if (char.IsWhiteSpace(input[i])) + { + index = i - start; + break; + } + } + + var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); + + var rangeStart = start; +#if NET5_0_OR_GREATER + if ((options & StringSplitOptions.TrimEntries) != 0) + { + var len = sp.Length; + sp = sp.TrimStart(); + rangeStart = start + len - sp.Length; + sp = sp.TrimEnd(); + } +#endif + + if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) + { + visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); + } + + if (index < 0) + { + return; + } + + start += index + SeparatorLen; + } + } + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// A character that delimits the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static void VisitSplits( + this string input, + char separator, + TContext context, + SplitVisitor visitor, + StringSplitOptions options = StringSplitOptions.None) + => VisitSplits(input.AsSpan(), separator, context, visitor, options); +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// The characters that delimit the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public static void VisitSplits( + this string input, + ReadOnlySpan separators, + TContext context, + SplitVisitor visitor, + StringSplitOptions options = StringSplitOptions.None) + => VisitSplits(input.AsSpan(), separators, context, visitor, options); +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// The strings that delimit the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this string input, + string[] separators, + TContext context, + SplitVisitor visitor, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + => VisitSplits(input.AsSpan(), separators, context, visitor, comparison, options); + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// The string that delimits the substrings in this instance. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// The kind of string comparison to apply to the separator strings. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this string input, + string separator, + TContext context, + SplitVisitor visitor, + StringComparison comparison = StringComparison.Ordinal, + StringSplitOptions options = StringSplitOptions.None) + => VisitSplits(input.AsSpan(), separator, context, visitor, comparison, options); + + /// + /// Invokes a delegate for individual string segments. + /// + /// The string to split. + /// An object that can be used to pass state to the visitor. + /// A delegate that gets invoked for each individual segment. + /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. + /// The type of the visitor's context. + /// + /// This uses whitespace as a separator of individual substrings. + /// + /// The visitor delegate is invoked for each segment in the input. It is given as parameter the + /// value of the argument, the segment index, and a range for the segment. + /// + public static void VisitSplits( + this string input, + TContext context, + SplitVisitor visitor, + StringSplitOptions options = StringSplitOptions.None) + => VisitSplits(input.AsSpan(), context, visitor, options); +} + +#endif diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs index 9dbe57266a4..0ced1c50c4a 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs @@ -55,4 +55,9 @@ public int Read(FileInfo file, int length, Span destination) return min; } + + public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) + { + return Enumerable.Empty>(); + } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs index 6450ff2b2f9..3b20e575af3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs @@ -91,4 +91,9 @@ public void ReplaceFileContent(FileInfo file, string value) { _fileContent[file.FullName] = value; } + + public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) + { + return Enumerable.Empty>(); + } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs index c6dce066a3f..35583f17224 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs @@ -20,7 +20,7 @@ public void Test_WindowsCountersOptionsCustomValidator_With_Wrong_IP_Address() { SourceIpAddresses = new HashSet { "" } }; - var tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); + var tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); var validator = new ResourceMonitoringOptionsCustomValidator(); var result = validator.Validate("", options); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index 2c584c7ea08..5f30cc9d8fd 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -234,11 +234,11 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - TcpTableInfo tcp6TableInfo = new TcpTableInfo(Options.Options.Create(options)); + WindowsTcpTableInfo tcp6TableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetIPv6CachingSnapshot(); + var tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); }); } @@ -250,11 +250,11 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - TcpTableInfo tcp6TableInfo = new TcpTableInfo(Options.Options.Create(options)); + WindowsTcpTableInfo tcp6TableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetIPv6CachingSnapshot(); + var tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); }); } @@ -268,9 +268,9 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - TcpTableInfo tcp6TableInfo = new TcpTableInfo(Options.Options.Create(options)); + WindowsTcpTableInfo tcp6TableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithFakeInformation); - var tcpStateInfo = tcp6TableInfo.GetIPv6CachingSnapshot(); + var tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -286,7 +286,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() Assert.Equal(1, tcpStateInfo.DeleteTcbCount); // Second calling in a small interval. - tcpStateInfo = tcp6TableInfo.GetIPv6CachingSnapshot(); + tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -303,7 +303,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() // Third calling in a long interval. Thread.Sleep(6000); - tcpStateInfo = tcp6TableInfo.GetIPv6CachingSnapshot(); + tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); Assert.NotNull(tcpStateInfo); Assert.Equal(2, tcpStateInfo.ClosedCount); Assert.Equal(2, tcpStateInfo.ListenCount); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs index 6ca26cf324a..8f6309085ec 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs @@ -177,11 +177,11 @@ public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() SourceIpAddresses = new HashSet { "127.0.0.1" }, SamplingInterval = DefaultTimeSpan }; - TcpTableInfo tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); + WindowsTcpTableInfo tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcpTableInfo.GetIPv4CachingSnapshot(); + var tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); }); } @@ -193,11 +193,11 @@ public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() SourceIpAddresses = new HashSet { "127.0.0.1" }, SamplingInterval = DefaultTimeSpan }; - TcpTableInfo tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); + WindowsTcpTableInfo tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcpTableInfo.GetIPv4CachingSnapshot(); + var tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); }); } @@ -211,9 +211,9 @@ public void Test_TcpTableInfo_Get_Correct_Information() SourceIpAddresses = new HashSet { "127.0.0.1" }, SamplingInterval = DefaultTimeSpan }; - TcpTableInfo tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); + WindowsTcpTableInfo tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithFakeInformation); - var tcpStateInfo = tcpTableInfo.GetIPv4CachingSnapshot(); + var tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -229,7 +229,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() Assert.Equal(1, tcpStateInfo.DeleteTcbCount); // Second calling in a small interval. - tcpStateInfo = tcpTableInfo.GetIPv4CachingSnapshot(); + tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -246,7 +246,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() // Third calling in a long interval. Thread.Sleep(6000); - tcpStateInfo = tcpTableInfo.GetIPv4CachingSnapshot(); + tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); Assert.NotNull(tcpStateInfo); Assert.Equal(2, tcpStateInfo.ClosedCount); Assert.Equal(2, tcpStateInfo.ListenCount); @@ -268,7 +268,7 @@ public void Test_TcpTableInfo_CalculateCount_default_branch() TcpStateInfo tcpStateInfo = new(); // Add this case to increase coverage, but 0 will not happen in actual case. - TcpTableInfo.CalculateCount(tcpStateInfo, 0); + WindowsTcpTableInfo.CalculateCount(tcpStateInfo, 0); Assert.NotNull(tcpStateInfo); Assert.Equal(0, tcpStateInfo.ClosedCount); Assert.Equal(0, tcpStateInfo.ListenCount); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs index 6370e26cb09..7eb1fdec01e 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs @@ -32,7 +32,7 @@ public void WindowsCounters_Registers_Instruments() meterFactoryMock.Setup(x => x.Create(It.IsAny())) .Returns(meter); - var tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); + var tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(TcpTableInfoTests.FakeGetTcpTableWithFakeInformation); tcpTableInfo.SetGetTcp6TableDelegate(Tcp6TableInfoTests.FakeGetTcp6TableWithFakeInformation); var windowsCounters = new WindowsNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); @@ -74,7 +74,7 @@ public void WindowsCounters_Got_Unsuccessful() meterFactoryMock.Setup(x => x.Create(It.IsAny())) .Returns(meter); - var tcpTableInfo = new TcpTableInfo(Options.Options.Create(options)); + var tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(TcpTableInfoTests.FakeGetTcpTableWithUnsuccessfulStatusAllTheTime); tcpTableInfo.SetGetTcp6TableDelegate(Tcp6TableInfoTests.FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); var windowsCounters = new WindowsNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); diff --git a/test/Shared/StringSplit/SplitExtensionsTests.cs b/test/Shared/StringSplit/SplitExtensionsTests.cs new file mode 100644 index 00000000000..bb96612658a --- /dev/null +++ b/test/Shared/StringSplit/SplitExtensionsTests.cs @@ -0,0 +1,426 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET8_0_OR_GREATER + +using System; +using System.Collections.Generic; +using Xunit; + +namespace Microsoft.Shared.StringSplit.Test; + +public static class SplitExtensionsTests +{ + public static IEnumerable SingleCharData => new List + { + new object[] { string.Empty, StringSplitOptions.None }, + new object[] { "A", StringSplitOptions.None }, + new object[] { "AA", StringSplitOptions.None }, + new object[] { "/", StringSplitOptions.None }, + new object[] { "A/", StringSplitOptions.None }, + new object[] { "AA/", StringSplitOptions.None }, + new object[] { "/A", StringSplitOptions.None }, + new object[] { "/AA", StringSplitOptions.None }, + new object[] { "AA/B", StringSplitOptions.None }, + new object[] { "AA//", StringSplitOptions.None }, + new object[] { "AA//BB", StringSplitOptions.None }, + + new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "/", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A/", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA/", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "/A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "/AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA/B", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA//", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA//BB", StringSplitOptions.RemoveEmptyEntries }, + +#if NET5_0_OR_GREATER + new object[] { string.Empty, StringSplitOptions.TrimEntries }, + new object[] { " ", StringSplitOptions.TrimEntries }, + new object[] { " A", StringSplitOptions.TrimEntries }, + new object[] { "AA", StringSplitOptions.TrimEntries }, + new object[] { "A A", StringSplitOptions.TrimEntries }, + new object[] { "/", StringSplitOptions.TrimEntries }, + new object[] { " /", StringSplitOptions.TrimEntries }, + new object[] { "A/", StringSplitOptions.TrimEntries }, + new object[] { "A /", StringSplitOptions.TrimEntries }, + new object[] { "AA/", StringSplitOptions.TrimEntries }, + new object[] { "/A", StringSplitOptions.TrimEntries }, + new object[] { " / A ", StringSplitOptions.TrimEntries }, + new object[] { "/AA", StringSplitOptions.TrimEntries }, + new object[] { "AA/B", StringSplitOptions.TrimEntries }, + new object[] { "AA//", StringSplitOptions.TrimEntries }, + new object[] { "AA//BB", StringSplitOptions.TrimEntries }, + new object[] { " abcde /fghijk /lmn", StringSplitOptions.TrimEntries }, +#endif + }; + + public static IEnumerable MultiCharData => new List + { + new object[] { string.Empty, StringSplitOptions.None }, + new object[] { "A", StringSplitOptions.None }, + new object[] { "AA", StringSplitOptions.None }, + new object[] { "/", StringSplitOptions.None }, + new object[] { "A\\", StringSplitOptions.None }, + new object[] { "AA/", StringSplitOptions.None }, + new object[] { "/A", StringSplitOptions.None }, + new object[] { "\\AA", StringSplitOptions.None }, + new object[] { "AA/B", StringSplitOptions.None }, + new object[] { "AA/\\", StringSplitOptions.None }, + new object[] { "AA//BB", StringSplitOptions.None }, + + new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "/", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A/", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA\\", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "/A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "/AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA/B", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA//", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA//BB", StringSplitOptions.RemoveEmptyEntries }, + +#if NET5_0_OR_GREATER + new object[] { string.Empty, StringSplitOptions.TrimEntries }, + new object[] { " ", StringSplitOptions.TrimEntries }, + new object[] { " A", StringSplitOptions.TrimEntries }, + new object[] { "AA", StringSplitOptions.TrimEntries }, + new object[] { "A A", StringSplitOptions.TrimEntries }, + new object[] { "/", StringSplitOptions.TrimEntries }, + new object[] { " /", StringSplitOptions.TrimEntries }, + new object[] { "A/", StringSplitOptions.TrimEntries }, + new object[] { "A /", StringSplitOptions.TrimEntries }, + new object[] { "AA/", StringSplitOptions.TrimEntries }, + new object[] { "/A", StringSplitOptions.TrimEntries }, + new object[] { " / A ", StringSplitOptions.TrimEntries }, + new object[] { "/AA", StringSplitOptions.TrimEntries }, + new object[] { "AA/B", StringSplitOptions.TrimEntries }, + new object[] { "AA//", StringSplitOptions.TrimEntries }, + new object[] { "AA//BB", StringSplitOptions.TrimEntries }, + new object[] { " abcde //fghijk //lmn", StringSplitOptions.TrimEntries }, +#endif + }; + + public static IEnumerable WhitespaceData => new List + { + new object[] { string.Empty, StringSplitOptions.None }, + new object[] { "A", StringSplitOptions.None }, + new object[] { "AA", StringSplitOptions.None }, + new object[] { " ", StringSplitOptions.None }, + new object[] { "A ", StringSplitOptions.None }, + new object[] { "AA ", StringSplitOptions.None }, + new object[] { " A", StringSplitOptions.None }, + new object[] { " AA", StringSplitOptions.None }, + new object[] { "AA B", StringSplitOptions.None }, + new object[] { "AA ", StringSplitOptions.None }, + new object[] { "AA BB", StringSplitOptions.None }, + + new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { " ", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A ", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA ", StringSplitOptions.RemoveEmptyEntries }, + new object[] { " A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { " AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA B", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA ", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA BB", StringSplitOptions.RemoveEmptyEntries }, + +#if NET5_0_OR_GREATER + new object[] { string.Empty, StringSplitOptions.TrimEntries }, + new object[] { " ", StringSplitOptions.TrimEntries }, + new object[] { " A", StringSplitOptions.TrimEntries }, + new object[] { "AA", StringSplitOptions.TrimEntries }, + new object[] { "A A", StringSplitOptions.TrimEntries }, + new object[] { " ", StringSplitOptions.TrimEntries }, + new object[] { " ", StringSplitOptions.TrimEntries }, + new object[] { "A ", StringSplitOptions.TrimEntries }, + new object[] { "A ", StringSplitOptions.TrimEntries }, + new object[] { "AA ", StringSplitOptions.TrimEntries }, + new object[] { " A", StringSplitOptions.TrimEntries }, + new object[] { " A ", StringSplitOptions.TrimEntries }, + new object[] { " AA", StringSplitOptions.TrimEntries }, + new object[] { "AA B", StringSplitOptions.TrimEntries }, + new object[] { "AA ", StringSplitOptions.TrimEntries }, + new object[] { "AA BB", StringSplitOptions.TrimEntries }, +#endif + }; + + public static IEnumerable StringData => new List + { + new object[] { string.Empty, StringSplitOptions.None }, + new object[] { "A", StringSplitOptions.None }, + new object[] { "AA", StringSplitOptions.None }, + new object[] { "XX", StringSplitOptions.None }, + new object[] { "AXX", StringSplitOptions.None }, + new object[] { "AAXX", StringSplitOptions.None }, + new object[] { "YYA", StringSplitOptions.None }, + new object[] { "XXAA", StringSplitOptions.None }, + new object[] { "AAXXB", StringSplitOptions.None }, + new object[] { "AAXXYY", StringSplitOptions.None }, + new object[] { "AAXXYYBB", StringSplitOptions.None }, + + new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, + new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "XX", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AXX", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AAYY", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "XXA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "XXAA", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AAXXB", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AAXX", StringSplitOptions.RemoveEmptyEntries }, + new object[] { "AAYYBB", StringSplitOptions.RemoveEmptyEntries }, + +#if NET5_0_OR_GREATER + new object[] { string.Empty, StringSplitOptions.TrimEntries }, + new object[] { "XX", StringSplitOptions.TrimEntries }, + new object[] { "YYA", StringSplitOptions.TrimEntries }, + new object[] { "AA", StringSplitOptions.TrimEntries }, + new object[] { "AXXA", StringSplitOptions.TrimEntries }, + new object[] { "YY", StringSplitOptions.TrimEntries }, + new object[] { "XXYY", StringSplitOptions.TrimEntries }, + new object[] { "AXX", StringSplitOptions.TrimEntries }, + new object[] { "AXXYY", StringSplitOptions.TrimEntries }, + new object[] { "AAXX", StringSplitOptions.TrimEntries }, + new object[] { "XA", StringSplitOptions.TrimEntries }, + new object[] { "XXYYXXAXX", StringSplitOptions.TrimEntries }, + new object[] { "XXAA", StringSplitOptions.TrimEntries }, + new object[] { "AAYYB", StringSplitOptions.TrimEntries }, + new object[] { "AAXXYY", StringSplitOptions.TrimEntries }, + new object[] { "AAYYXXBB", StringSplitOptions.TrimEntries }, + new object[] { " abcde XXfghijk YYlmn", StringSplitOptions.TrimEntries }, +#endif + }; + + [Theory] + [MemberData(nameof(SingleCharData))] + public static void SingleChar(string input, StringSplitOptions options) + { + var expected = input.Split(new[] { '/' }, options); + + var actual = new StringRange[20]; + Assert.True(input.TrySplit('/', actual, out int numActuals, options)); + + Assert.Equal(expected.Length, numActuals); + Assert.Equal(expected.Length == 0, input.TrySplit('/', Array.Empty(), out _, options)); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); + } + } + + [Theory] + [MemberData(nameof(MultiCharData))] + public static void MultiChar(string input, StringSplitOptions options) + { + var expected = input.Split(new[] { '/', '\\' }, options); + + var actual = new StringRange[20]; + Assert.True(input.TrySplit(new[] { '/', '\\' }, actual, out int numActuals, options)); + + Assert.Equal(expected.Length, numActuals); + Assert.Equal(expected.Length == 0, input.TrySplit(new[] { '/', '\\' }, Array.Empty(), out _, options)); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); + } + } + + [Theory] + [MemberData(nameof(WhitespaceData))] + public static void Whitespace(string input, StringSplitOptions options) + { + var expected = input.Split((string[]?)null, options); + + var actual = new StringRange[20]; + Assert.True(input.TrySplit(actual, out int numActuals, options)); + + Assert.Equal(expected.Length, numActuals); + Assert.Equal(expected.Length == 0, input.TrySplit(Array.Empty(), out _, options)); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); + } + } + + [Theory] + [MemberData(nameof(StringData))] + public static void StringArray(string input, StringSplitOptions options) + { + var expected = input.Split(new[] { "XX", "YY" }, options); + + var actual = new StringRange[20]; + Assert.True(input.TrySplit(new[] { "XX", "YY" }, actual, out int numActuals, StringComparison.Ordinal, options)); + + Assert.Equal(expected.Length, numActuals); + Assert.Equal(expected.Length == 0, input.TrySplit(new[] { "XX", "YY" }, Array.Empty(), out _, StringComparison.Ordinal, options)); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); + } + } + +#if NETCOREAPP3_1_OR_GREATER + [Theory] + [MemberData(nameof(StringData))] + public static void Strings(string input, StringSplitOptions options) + { + for (int i = 0; i < 2; i++) + { + var separator = i == 0 ? "XX" : "YY"; + + var expected = input.Split(separator, options); + + var actual = new StringRange[20]; + Assert.True(input.TrySplit(separator, actual, out int numActuals, StringComparison.Ordinal, options)); + + Assert.Equal(expected.Length, numActuals); + Assert.Equal(expected.Length == 0, input.TrySplit(separator, Array.Empty(), out _, StringComparison.Ordinal, options)); + + for (int j = 0; j < expected.Length; j++) + { + Assert.Equal(expected[j], input.Substring(actual[j].Index, actual[j].Count)); + } + } + } +#endif + + [Theory] + [MemberData(nameof(SingleCharData))] + public static void VisitSingleChar(string input, StringSplitOptions options) + { + var expected = input.Split(new[] { '/' }, options); + + var actual = new string[20]; + int numActuals = 0; + input.VisitSplits('/', actual, (s, c, a) => + { + a[c] = s.ToString(); + numActuals++; + }, options); + + Assert.Equal(expected.Length, numActuals); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + + [Theory] + [MemberData(nameof(MultiCharData))] + public static void VisitMultiChar(string input, StringSplitOptions options) + { + var expected = input.Split(new[] { '/', '\\' }, options); + + var actual = new string[20]; + int numActuals = 0; + input.VisitSplits(new[] { '/', '\\' }, actual, (s, c, a) => + { + a[c] = s.ToString(); + numActuals++; + }, options); + + Assert.Equal(expected.Length, numActuals); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + + [Theory] + [MemberData(nameof(WhitespaceData))] + public static void VisitWhitespace(string input, StringSplitOptions options) + { + var expected = input.Split((string[]?)null, options); + + var actual = new string[20]; + int numActuals = 0; + input.VisitSplits(actual, (s, c, a) => + { + a[c] = s.ToString(); + numActuals++; + }, options); + + Assert.Equal(expected.Length, numActuals); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + + [Theory] + [MemberData(nameof(StringData))] + public static void VisitStringArray(string input, StringSplitOptions options) + { + var expected = input.Split(new[] { "XX", "YY" }, options); + + var actual = new string[20]; + int numActuals = 0; + input.VisitSplits(new[] { "XX", "YY" }, actual, (s, c, a) => + { + a[c] = s.ToString(); + numActuals++; + }, StringComparison.Ordinal, options); + + Assert.Equal(expected.Length, numActuals); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + +#if NETCOREAPP3_1_OR_GREATER + [Theory] + [MemberData(nameof(StringData))] + public static void VisitStrings(string input, StringSplitOptions options) + { + for (int i = 0; i < 2; i++) + { + var separator = i == 0 ? "XX" : "YY"; + + var expected = input.Split(separator, options); + + var actual = new string[20]; + int numActuals = 0; + input.VisitSplits(separator, actual, (s, c, a) => + { + a[c] = s.ToString(); + numActuals++; + }, StringComparison.Ordinal, options); + + Assert.Equal(expected.Length, numActuals); + + for (int j = 0; j < expected.Length; j++) + { + Assert.Equal(expected[j], actual[j]); + } + } + } +#endif + + [Fact] + public static void CheckOpts() + { + var ss = new StringRange[1]; + Assert.Throws(() => "ABC".TrySplit(new[] { '/', '\\' }, ss, out _, (StringSplitOptions)(-1))); + Assert.Throws(() => "ABC".TrySplit(new[] { "XX", "YY" }, ss, out _, StringComparison.Ordinal, (StringSplitOptions)(-1))); + Assert.Throws(() => "ABC".TrySplit('/', ss, out _, (StringSplitOptions)(-1))); + Assert.Throws(() => "ABC".TrySplit(ss, out _, (StringSplitOptions)(-1))); + } +} + +#endif diff --git a/test/Shared/StringSplit/StringRangeTests.cs b/test/Shared/StringSplit/StringRangeTests.cs new file mode 100644 index 00000000000..faf7e9b794e --- /dev/null +++ b/test/Shared/StringSplit/StringRangeTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#if !NET8_0_OR_GREATER + +using System; +using Xunit; + +namespace Microsoft.Shared.StringSplit.Test; + +public static class StringRangeTests +{ + [Fact] + public static void Operators() + { + var ss = new StringRange(1, 2); + var ss2 = new StringRange(2, 2); + + Assert.True(ss.Equals(ss)); + Assert.True(ss.Equals(new StringRange(1, 2))); + Assert.True(ss.Equals((object)ss)); + Assert.False(ss.Equals(new object())); + Assert.False(ss.Equals(new StringRange(1, 3))); + + Assert.Equal(ss.GetHashCode(), ss.GetHashCode()); + + Assert.True(ss == new StringRange(1, 2)); + Assert.True(ss != ss2); + Assert.True(ss.CompareTo(ss2) < 0); + Assert.True(ss.CompareTo((object)ss2) < 0); + Assert.True(ss.CompareTo(null) == 1); + Assert.True(ss < ss2); + Assert.True(ss <= ss2); + Assert.True(ss <= new StringRange(1, 2)); + Assert.True(ss2 > ss); + Assert.True(ss2 >= ss); + Assert.True(ss2 >= new StringRange(2, 2)); + + Assert.Throws(() => ss.CompareTo(new object())); + } +} + +#endif From beaf2aaad8093cfdcd61e837cab4f81b1eb225fc Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:51:47 +0200 Subject: [PATCH 02/13] add tests --- .../Network/LinuxNetworkUtilizationParser.cs | 21 +- .../Linux/Network/LinuxTcpTableInfo.cs | 9 +- ...ceMonitoringServiceCollectionExtensions.cs | 1 + .../Linux/LinuxCountersTests.cs | 217 ++++++++++++++++++ .../LinuxNetworkUtilizationParserTests.cs | 116 ++++++++++ .../Linux/OSFileSystemTests.cs | 145 ++++++++++++ .../Resources/FileNamesOnlyFileSystem.cs | 8 +- .../Resources/HardcodedValueFileSystem.cs | 22 +- .../fixtures/tcp | 16 ++ .../fixtures/tcp6 | 15 ++ .../fixtures/tcpacct.stat | 14 ++ 11 files changed, 570 insertions(+), 14 deletions(-) create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp6 create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcpacct.stat diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index 48f5ee90c29..3669e8c4602 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -3,7 +3,6 @@ using System; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Extensions.ObjectPool; #if !NET8_0_OR_GREATER @@ -49,18 +48,26 @@ public LinuxNetworkUtilizationParser(IFileSystem fileSystem) /// The method is used in the LinuxUtilizationParser class to read Span data and calculate the TCP state info. /// Refer proc net tcp. /// - [SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", - Justification = "We are adding another digit, so we need to multiply by ten.")] private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo tcpStateInfo) { + const int Base16 = 16; ReadOnlySpan line = buffer.TrimStart(); - const int Target = 4; #if NET8_0_OR_GREATER + const int Target = 5; Span range = stackalloc Range[Target]; - int numRanges = line.Split(range, ' '); + + // on .NET 8+, if capacity of destination range array is less than number of ranges found by ReadOnlySpan.Split(), + // the last range in the array will get all the remaining elements of the ReadOnlySpan. + // therefore we request 5 ranges instead of 4, and then range[Target - 2] will have the range we need without the remaining elements. + int numRanges = line.Split(range, ' ', StringSplitOptions.RemoveEmptyEntries); #else + const int Target = 4; Span range = stackalloc StringRange[Target]; + + // in our StringRange API, if capacity of destination range array is less than number of ranges found by ReadOnlySpan.TrySplit(), + // the last range in the array will get the last range as expected, and all remaining elements will be ignored. + // hence range[Target - 1] will have the last range as we need. _ = line.TrySplit(" ", range, out int numRanges, StringComparison.OrdinalIgnoreCase, StringSplitOptions.RemoveEmptyEntries); #endif if (numRanges < Target) @@ -69,14 +76,14 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t } #if NET8_0_OR_GREATER - ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 1].Start.Value, range[Target - 1].End.Value - range[Target - 1].Start.Value); + ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 2].Start.Value, range[Target - 2].End.Value - range[Target - 2].Start.Value); #else ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 1].Index, range[Target - 1].Count); #endif // until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 // we have to allocate & throw away memory using ToString(): - switch ((LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), 16)) + switch ((LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), Base16)) { case LinuxTcpState.ESTABLISHED: tcpStateInfo.EstabCount++; diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs index 69b796b1e7a..5ca031ff440 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs @@ -11,17 +11,16 @@ internal class LinuxTcpTableInfo : ITcpTableInfo private readonly object _lock = new(); private readonly TimeSpan _samplingInterval; private readonly LinuxNetworkUtilizationParser _parser; - private readonly TimeProvider _timeProvider; + private static TimeProvider TimeProvider => TimeProvider.System; private TcpStateInfo _iPv4Snapshot = new(); private TcpStateInfo _iPv6Snapshot = new(); private DateTimeOffset _refreshAfter; - public LinuxTcpTableInfo(IOptions options, LinuxNetworkUtilizationParser parser, TimeProvider timeProvider) + public LinuxTcpTableInfo(IOptions options, LinuxNetworkUtilizationParser parser) { _samplingInterval = options.Value.SamplingInterval; _parser = parser; - _timeProvider = timeProvider; } public TcpStateInfo GetIpV4CachingSnapshot() @@ -40,11 +39,11 @@ private void RefreshSnapshotIfNeeded() { lock (_lock) { - if (_refreshAfter < _timeProvider.GetUtcNow()) + if (_refreshAfter < TimeProvider.GetUtcNow()) { _iPv4Snapshot = _parser.GetTcpIPv4StateInfo(); _iPv6Snapshot = _parser.GetTcpIPv6StateInfo(); - _refreshAfter = _timeProvider.GetUtcNow().Add(_samplingInterval); + _refreshAfter = TimeProvider.GetUtcNow().Add(_samplingInterval); } } } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs index 83db13a3cd4..ab206eeda11 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs @@ -120,6 +120,7 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.PickLinuxParser(); _ = builder.Services diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs new file mode 100644 index 00000000000..70aebaedadb --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.IO; +using System.Threading; +using FluentAssertions; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; + +public class LinuxCountersTests +{ + [Fact] + public void LinuxNetworkCounters_Registers_Instruments() + { + var fileSystem = new HardcodedValueFileSystem(new Dictionary + { + { + new FileInfo("/proc/net/tcp"), " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF4 C1B17824:01BB 05 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF4 C1B17824:01BB 05 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + }, + { + new FileInfo("/proc/net/tcp6"), + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 " + + "0A 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 " + + "0B 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 " + + "02 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 " + + "03 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 " + + "04 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 " + + "05 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 " + + "06 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 " + + "07 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A " + + "08 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B " + + "09 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 " + + "0A 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 " + + "0B 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 " + + "02 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 " + + "03 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 " + + "04 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 " + + "05 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 " + + "06 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 " + + "07 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A " + + "08 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B " + + "09 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + }, + }); + + var parser = new LinuxNetworkUtilizationParser(fileSystem); + var options = Microsoft.Extensions.Options.Options.Create(new()); + + using var meter = new Meter(nameof(LinuxNetworkMetrics)); + var meterFactoryMock = new Mock(); + meterFactoryMock + .Setup(x => x.Create(It.IsAny())) + .Returns(meter); + + var tcpTableInfo = new LinuxTcpTableInfo(options, parser); + var lnm = new LinuxNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); + + using var listener = new MeterListener + { + InstrumentPublished = (instrument, listener) => + { + if (ReferenceEquals(meter, instrument.Meter)) + { + listener.EnableMeasurementEvents(instrument); + } + } + }; + + var samples = new List<(Instrument instrument, long value)>(); + listener.SetMeasurementEventCallback((instrument, value, _, _) => + { + samples.Add((instrument, value)); + }); + + listener.Start(); + listener.RecordObservableInstruments(); + samples.Count.Should().Be(22); + samples.Should().AllSatisfy(x => x.instrument.Name.Should().Be(ResourceUtilizationInstruments.SystemNetworkConnections)); + samples.Should().AllSatisfy(x => x.value.Should().Be(2)); + } + + [Fact] + public void Tcp_State_Info_Changes_After_Time() + { + var fileSystem = new HardcodedValueFileSystem(new Dictionary + { + { + new FileInfo("/proc/net/tcp"), + " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 0: 030011AC:8AF8 C1B17828:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + }, + { + new FileInfo("/proc/net/tcp6"), + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 0 3 0000000000000000\r\n" + }, + }); + + var parser = new LinuxNetworkUtilizationParser(fileSystem); + + var tcpIPv4StateInfo = parser.GetTcpIPv4StateInfo(); + var tcpIPv6StateInfo = parser.GetTcpIPv6StateInfo(); + Assert.Equal(2, tcpIPv4StateInfo.EstabCount); + Assert.Equal(0, tcpIPv4StateInfo.SynSentCount); + Assert.Equal(3, tcpIPv6StateInfo.EstabCount); + Assert.Equal(0, tcpIPv6StateInfo.SynSentCount); + + Thread.Sleep(3000); + var tcpFile = + " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 0: 030011AC:8AF8 C1B17828:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n"; + var tcp6File = + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C " + + "01 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D " + + "02 00000000:00000000 00:00000000 00000000 " + + "472 0 0 3 0000000000000000\r\n"; + fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp"), tcpFile); + fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); + Thread.Sleep(3000); + + tcpIPv4StateInfo = parser.GetTcpIPv4StateInfo(); + tcpIPv6StateInfo = parser.GetTcpIPv6StateInfo(); + Assert.Equal(1, tcpIPv4StateInfo.EstabCount); + Assert.Equal(1, tcpIPv4StateInfo.SynSentCount); + Assert.Equal(2, tcpIPv6StateInfo.EstabCount); + Assert.Equal(1, tcpIPv6StateInfo.SynSentCount); + } +} diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs new file mode 100644 index 00000000000..10382c3182e --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; +using Xunit; + +namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; + +public sealed class LinuxNetworkUtilizationParserTests +{ + [Theory] + [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] + [InlineData("")] + [InlineData("________________________Asdasdasdas dd")] + [InlineData(" ")] + [InlineData("!@#!$%!@")] + public void Parser_Throws_When_Data_Is_Invalid(string line) + { + var parser = new LinuxNetworkUtilizationParser(new HardcodedValueFileSystem(line)); + + Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + } + + [Fact] + public void Parser_Tcp_State_Info_Exception() + { + var fileSystem = new HardcodedValueFileSystem(new Dictionary + { + { + new FileInfo("/proc/net/tcp"), " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2\r\n" + }, + { + new FileInfo("/proc/net/tcp6"), + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000\r\n" + }, + }); + + var parser = new LinuxNetworkUtilizationParser(fileSystem); + + // Exception Message: Could not split contents. We expected every line to contains more than 4 elements, but it has only 2 elements. + Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + + // Exception Message: Could not split contents. We expected every line to contains more than 4 elements, but it has only 3 elements. + Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + + string tcpFile = + " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 00 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n"; + string tcp6File = + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + + "12 00000000:00000000 00:00000000 00000000 " + + "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n"; + + fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp"), tcpFile); + fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); + + // Cannot find status: 00 in LINUX_TCP_STATE + Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + + // Cannot find status: 12 in LINUX_TCP_STATE + Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + + tcpFile = ""; + tcp6File = ""; + + fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp"), tcpFile); + fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); + + // Could not parse '/proc/net/tcp'. File was empty. + Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + + // Could not parse '/proc/net/tcp6'. File was empty. + Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + } + + [Fact] + public void Parser_Tcp_State_Info() + { + var parser = new LinuxNetworkUtilizationParser(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation)); + var tcp4StateInfo = parser.GetTcpIPv4StateInfo(); + + Assert.Equal(2, tcp4StateInfo.EstabCount); + Assert.Equal(1, tcp4StateInfo.SynSentCount); + Assert.Equal(1, tcp4StateInfo.SynRcvdCount); + Assert.Equal(1, tcp4StateInfo.FinWait1Count); + Assert.Equal(0, tcp4StateInfo.FinWait2Count); + Assert.Equal(3, tcp4StateInfo.TimeWaitCount); + Assert.Equal(2, tcp4StateInfo.ClosedCount); + Assert.Equal(2, tcp4StateInfo.CloseWaitCount); + Assert.Equal(1, tcp4StateInfo.LastAckCount); + Assert.Equal(1, tcp4StateInfo.ListenCount); + Assert.Equal(1, tcp4StateInfo.ClosingCount); + + var tcp6StateInfo = parser.GetTcpIPv6StateInfo(); + Assert.Equal(4, tcp6StateInfo.EstabCount); + Assert.Equal(1, tcp6StateInfo.SynSentCount); + Assert.Equal(1, tcp6StateInfo.SynRcvdCount); + Assert.Equal(1, tcp6StateInfo.FinWait1Count); + Assert.Equal(1, tcp6StateInfo.FinWait2Count); + Assert.Equal(1, tcp6StateInfo.TimeWaitCount); + Assert.Equal(1, tcp6StateInfo.ClosedCount); + Assert.Equal(1, tcp6StateInfo.CloseWaitCount); + Assert.Equal(1, tcp6StateInfo.LastAckCount); + Assert.Equal(1, tcp6StateInfo.ListenCount); + Assert.Equal(1, tcp6StateInfo.ClosingCount); + } + +} diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs index 2c9ee280414..b16844ae605 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/OSFileSystemTests.cs @@ -3,6 +3,8 @@ using System; using System.IO; +using System.Linq; +using System.Text; using Microsoft.Shared.Pools; using Microsoft.TestUtilities; using Xunit; @@ -65,4 +67,147 @@ public void Reading_Small_Portion_Of_Big_File_Works(int length) Assert.True(length >= written); Assert.True(b.AsSpan(0, written).SequenceEqual(new string(Content, written).AsSpan())); } + + [Fact] + public void Reading_Line_By_Line_From_File_Works() + { + var fileSystem = new OSFileSystem(); + var file = new FileInfo("fixtures/tcpacct.stat"); + var bw = new BufferWriter(); + var index = 1; + foreach (var line in fileSystem.ReadAllByLines(file, bw)) + { + var expected = string.Format("line {0}", index); + var actual = line.ToString().Replace("\r", ""); + Assert.Equal(expected, actual); + index++; + } + + Assert.Equal(15, index); + } + + [Fact] + public void ReadAllByLines_Returns_Correct_Lines() + { + // Arrange + var fileContent = "Line 1\nLine 2\nLine 3\n"; + var fileBytes = Encoding.ASCII.GetBytes(fileContent); + var fileStream = new MemoryStream(fileBytes); + var fileInfo = new FileInfo("test.txt"); + var osFileSystem = new OSFileSystem(); + + // Write the file content to the file + using (var file = fileInfo.Create()) + { + file.Write(fileBytes, 0, fileBytes.Length); + } + + // Act + var lines = osFileSystem.ReadAllByLines(fileInfo, new BufferWriter()); + + // Assert + var expectedLines = new[] { "Line 1", "Line 2", "Line 3" }; + var i = 0; + foreach (var line in lines) + { + Assert.Equal(expectedLines[i], line.ToString()); + i++; + } + } + + [Fact] + public void ReadAllByLines_Returns_Empty_Sequence_For_Empty_File() + { + // Arrange + var fileInfo = new FileInfo("test.txt"); + var osFileSystem = new OSFileSystem(); + + // Write an empty file + using (var file = fileInfo.Create()) + { + // Do nothing + } + + // Act + var lines = osFileSystem.ReadAllByLines(fileInfo, new BufferWriter()); + + // Assert + Assert.Empty(lines); + } + + [Fact] + public void ReadAllByLines_Returns_Single_Line_For_Single_Line_File() + { + // Arrange + var fileContent = "Line 1"; + var fileBytes = Encoding.ASCII.GetBytes(fileContent); + var fileInfo = new FileInfo("test.txt"); + var osFileSystem = new OSFileSystem(); + + // Write the file content to the file + using (var file = fileInfo.Create()) + { + file.Write(fileBytes, 0, fileBytes.Length); + } + + // Act + var lines = osFileSystem.ReadAllByLines(fileInfo, new BufferWriter()); + + // Assert + var expectedLines = new[] { "Line 1" }; + var i = 0; + foreach (var line in lines) + { + Assert.Equal(expectedLines[i], line.ToString()); + i++; + } + } + + [Fact] + public void ReadAllByLines_Returns_Empty_Sequence_For_Nonexistent_File() + { + // Arrange + var fileInfo = new FileInfo("nonexistent.txt"); + var osFileSystem = new OSFileSystem(); + + // Act + // Assert + Assert.Throws(() => osFileSystem.ReadAllByLines(fileInfo, new BufferWriter()).ToList()); + } + + [Fact] + public void ReadAllByLines_Returns_Correct_Lines_For_Large_File() + { + // Arrange + // Large line counts of file (Should always be more than 420) + var count = 900000; + var fileContent = new StringBuilder(); + for (var i = 0; i < count; i++) + { + fileContent.AppendLine($"Line {i}"); + } + + var fileBytes = Encoding.ASCII.GetBytes(fileContent.ToString()); + var fileStream = new MemoryStream(fileBytes); + var fileInfo = new FileInfo("test.txt"); + var osFileSystem = new OSFileSystem(); + + // Write the file content to the file + using (var file = fileInfo.Create()) + { + file.Write(fileBytes, 0, fileBytes.Length); + } + + // Act + var lines = osFileSystem.ReadAllByLines(fileInfo, new BufferWriter()); + + // Assert + var expectedLines = Enumerable.Range(0, count).Select(i => $"Line {i}"); + var cnt = 0; + foreach (var line in lines) + { + Assert.Equal(expectedLines.ElementAt(cnt), line.ToString().Replace("\r", "")); + cnt++; + } + } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs index 0ced1c50c4a..a9e0fb0fbc3 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs @@ -58,6 +58,12 @@ public int Read(FileInfo file, int length, Span destination) public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) { - return Enumerable.Empty>(); + var a = File.ReadAllLines($"{_directory}/{file.Name}"); + foreach (var item in a) + { + destination.Reset(); + destination.Write(item); + yield return destination.WrittenMemory; + } } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs index 3b20e575af3..ac83153ceeb 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs @@ -94,6 +94,26 @@ public void ReplaceFileContent(FileInfo file, string value) public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) { - return Enumerable.Empty>(); + var flag = !_fileContent.TryGetValue(file.FullName, out var content); + if (_fileContent.Count == 0 || flag) + { + destination.Reset(); + destination.Write(_fallback); + yield return destination.WrittenMemory; + } + else + { + if (content != null) + { + var start = 0; + for (var newLineIndex = content.IndexOf('\n'); newLineIndex >= 0; newLineIndex = content.IndexOf('\n', newLineIndex + 1)) + { + destination.Reset(); + destination.Write(newLineIndex != -1 ? content.Substring(start, newLineIndex - start) : content); + start = newLineIndex + 1; + yield return destination.WrittenMemory; + } + } + } } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp new file mode 100644 index 00000000000..7f1a3a0b58e --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp @@ -0,0 +1,16 @@ + sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1 + 2: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 3: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1 + 4: FE043C0A:D91A B4492A14:01BB 06 00000000:00000000 03:000016E9 00000000 0 0 0 3 0000000000000000 + 5: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1 + 6: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 7: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 + 8: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 9: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1 + 10: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 11: 030011AC:8AF8 C1B17828:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 12: 030011AC:DEA3 856CC7B1:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1 + 13: 030011AC:8AF9 C1B17829:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + 14: 030011AC:DEA4 856CC7B2:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1 diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp6 b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp6 new file mode 100644 index 00000000000..cae264ba1bc --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcp6 @@ -0,0 +1,15 @@ + sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode + 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 0B 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 02 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 03 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 04 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 05 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 06 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 07 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A 08 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B 09 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 + 0: 00000000000000000000000000000000:0BC8 00000000000000000000000000000000:000E 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0 diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcpacct.stat b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcpacct.stat new file mode 100644 index 00000000000..d4f93aada31 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/fixtures/tcpacct.stat @@ -0,0 +1,14 @@ +line 1 +line 2 +line 3 +line 4 +line 5 +line 6 +line 7 +line 8 +line 9 +line 10 +line 11 +line 12 +line 13 +line 14 From f812f9545ea89f2d11aa4bb48e26fb7284206e48 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:43:32 +0200 Subject: [PATCH 03/13] Improve tests --- eng/MSBuild/Shared.props | 2 +- .../Network/LinuxNetworkUtilizationParser.cs | 6 +- ...ceMonitoringServiceCollectionExtensions.cs | 10 +- .../Linux/LinuxCountersTests.cs | 172 ++++++------------ .../LinuxNetworkUtilizationParserTests.cs | 57 +++--- .../Resources/FileNamesOnlyFileSystem.cs | 6 +- .../Resources/HardcodedValueFileSystem.cs | 2 +- ...tworkUtilizationParserTests.1.verified.txt | 10 + ...tworkUtilizationParserTests.2.verified.txt | 10 + ...tworkUtilizationParserTests.3.verified.txt | 9 + ...tworkUtilizationParserTests.4.verified.txt | 9 + ...tworkUtilizationParserTests.5.verified.txt | 9 + ...tworkUtilizationParserTests.6.verified.txt | 9 + ...r_Throws_When_Data_Is_Invalid.verified.txt | 1 + ...s_When_Data_Is_Invalid_line= .verified.txt | 1 + ...Data_Is_Invalid_line=!@#!$%!@.verified.txt | 1 + ...ws_When_Data_Is_Invalid_line=.verified.txt | 1 + ...ne=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt | 9 + ...______Asdasdasdas dd.verified.txt | 1 + ...zationParserTests.ipv4_line= .verified.txt | 9 + ...arserTests.ipv4_line=!@#!$%!@.verified.txt | 9 + ...izationParserTests.ipv4_line=.verified.txt | 9 + ...ne=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt | 9 + ...______Asdasdasdas dd.verified.txt | 9 + ...zationParserTests.ipv6_line= .verified.txt | 9 + ...arserTests.ipv6_line=!@#!$%!@.verified.txt | 9 + ...izationParserTests.ipv6_line=.verified.txt | 9 + ...ne=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt | 9 + ...______Asdasdasdas dd.verified.txt | 9 + 29 files changed, 259 insertions(+), 156 deletions(-) create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.5.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.6.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line= .verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=!@#!$%!@.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=________________________Asdasdasdas dd.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line= .verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=!@#!$%!@.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=________________________Asdasdasdas dd.verified.txt diff --git a/eng/MSBuild/Shared.props b/eng/MSBuild/Shared.props index 14fa8b868cd..a68b0e4298f 100644 --- a/eng/MSBuild/Shared.props +++ b/eng/MSBuild/Shared.props @@ -39,7 +39,7 @@ - + diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index 3669e8c4602..f622133eed9 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -45,7 +45,7 @@ public LinuxNetworkUtilizationParser(IFileSystem fileSystem) } /// - /// The method is used in the LinuxUtilizationParser class to read Span data and calculate the TCP state info. + /// The method is used to read Span data and calculate the TCP state info. /// Refer proc net tcp. /// private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo tcpStateInfo) @@ -124,11 +124,11 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t } /// - /// Reads the contents of a file located at file and parses it to extract information about the TCP/IP state info on the system. + /// Reads the contents of a file and parses it to extract information about the TCP/IP state info on the system. /// private TcpStateInfo GetTcpStateInfo(FileInfo file) { - // The value we are interested in starts with this. We just want to make sure it is true. + // The value we are interested in starts with this "sl". const string Sl = "sl"; var tcpStateInfo = new TcpStateInfo(); using ReturnableBufferWriter bufferWriter = new(_sharedBufferWriterPool); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs index ab206eeda11..6e1343aa9b6 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs @@ -90,9 +90,7 @@ private static ResourceMonitorBuilder AddWindowsProvider(this ResourceMonitorBui builder.PickWindowsSnapshotProvider(); _ = builder.Services - .AddActivatedSingleton(); - - _ = builder.Services + .AddActivatedSingleton() .AddActivatedSingleton(); return builder; @@ -120,13 +118,11 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); builder.PickLinuxParser(); _ = builder.Services - .AddActivatedSingleton(); - - _ = builder.Services + .AddActivatedSingleton() + .AddActivatedSingleton() .AddActivatedSingleton(); return builder; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs index 70aebaedadb..40ffa7e011d 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs @@ -19,101 +19,59 @@ public void LinuxNetworkCounters_Registers_Instruments() { var fileSystem = new HardcodedValueFileSystem(new Dictionary { - { - new FileInfo("/proc/net/tcp"), " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF4 C1B17824:01BB 05 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF4 C1B17824:01BB 05 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + - " 1: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + - " 0: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" - }, - { - new FileInfo("/proc/net/tcp6"), - " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 " + - "0A 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 " + - "0B 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 " + - "02 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 " + - "03 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 " + - "04 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 " + - "05 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 " + - "06 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 " + - "07 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A " + - "08 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B " + - "09 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 " + - "0A 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 " + - "0B 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 " + - "02 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 " + - "03 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 " + - "04 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 " + - "05 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 " + - "06 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 " + - "07 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A " + - "08 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B " + - "09 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" - }, + { + new FileInfo("/proc/net/tcp"), + " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF4 C1B17824:01BB 05 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAC 856CC7B9:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF3 C1B17823:01BB 03 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAD 856CC7BA:01BB 04 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF4 C1B17824:01BB 05 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAE 856CC7BB:01BB 06 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF5 C1B17825:01BB 07 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEAF 856CC7BC:01BB 08 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF6 C1B17826:01BB 09 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + + " 1: 030011AC:DEA1 856CC7BD:01BB 0A 00000000:00000000 02:000000D1 00000000 472 0 2483767 2 0000000014121fd6 28 4 30 10 -1\r\n" + + " 0: 030011AC:8AF7 C1B17827:01BB 0B 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n" + }, + { +#pragma warning disable S103 // Lines should not be too long - disabled for better readability + new FileInfo("/proc/net/tcp6"), + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 0B 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 02 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 03 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 04 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 05 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 06 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 07 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A 08 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B 09 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB9 00000000000000000000000000000000:0001 0B 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBC 00000000000000000000000000000000:0004 02 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBD 00000000000000000000000000000000:0005 03 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BBE 00000000000000000000000000000000:0006 04 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB1 00000000000000000000000000000000:0007 05 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB2 00000000000000000000000000000000:0008 06 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB3 00000000000000000000000000000000:0009 07 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB4 00000000000000000000000000000000:000A 08 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB5 00000000000000000000000000000000:000B 09 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + }, }); var parser = new LinuxNetworkUtilizationParser(fileSystem); @@ -166,15 +124,9 @@ public void Tcp_State_Info_Changes_After_Time() { new FileInfo("/proc/net/tcp6"), " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 0 3 0000000000000000\r\n" + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D 01 00000000:00000000 00:00000000 00000000 472 0 0 3 0000000000000000\r\n" }, }); @@ -194,15 +146,9 @@ public void Tcp_State_Info_Changes_After_Time() " 0: 030011AC:8AF8 C1B17828:01BB 02 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n"; var tcp6File = " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C " + - "01 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + - " 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D " + - "02 00000000:00000000 00:00000000 00000000 " + - "472 0 0 3 0000000000000000\r\n"; + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB6 0000000000000000FFFF000000000000:000C 01 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n" + + " 0: 00000000000000000000000000000000:0BB7 0000000000000000FFFF000000000000:000D 02 00000000:00000000 00:00000000 00000000 472 0 0 3 0000000000000000\r\n"; fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp"), tcpFile); fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); Thread.Sleep(3000); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs index 10382c3182e..1aaf633b755 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs @@ -1,72 +1,74 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; +using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; +using VerifyXunit; using Xunit; namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Test; +[UsesVerify] public sealed class LinuxNetworkUtilizationParserTests { + private const string VerifiedDataDirectory = "Verified"; + [Theory] [InlineData("DFIJEUWGHFWGBWEFWOMDOWKSLA")] [InlineData("")] [InlineData("________________________Asdasdasdas dd")] [InlineData(" ")] [InlineData("!@#!$%!@")] - public void Parser_Throws_When_Data_Is_Invalid(string line) + public async Task Parser_Throws_When_Data_Is_Invalid(string line) { var parser = new LinuxNetworkUtilizationParser(new HardcodedValueFileSystem(line)); - Assert.Throws(() => parser.GetTcpIPv4StateInfo()); - Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseParameters(line).UseMethodName("ipv4").UseDirectory(VerifiedDataDirectory); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseParameters(line).UseMethodName("ipv6").UseDirectory(VerifiedDataDirectory); } [Fact] - public void Parser_Tcp_State_Info_Exception() + public async Task Parser_Tcp_State_Info_Exception() { var fileSystem = new HardcodedValueFileSystem(new Dictionary { - { - new FileInfo("/proc/net/tcp"), " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 030011AC:8AF2\r\n" - }, - { - new FileInfo("/proc/net/tcp6"), - " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000\r\n" - }, + { + new FileInfo("/proc/net/tcp"), + " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 030011AC:8AF2\r\n" + }, + { + new FileInfo("/proc/net/tcp6"), + " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + + " 0: 00000000000000000000000000000000:0BB8 00000000000000000000000000000000:0000\r\n" + }, }); var parser = new LinuxNetworkUtilizationParser(fileSystem); - // Exception Message: Could not split contents. We expected every line to contains more than 4 elements, but it has only 2 elements. - Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + // Expected every line to contain more than 4 elements, but it has only 2 elements. + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseMethodName("1").UseDirectory(VerifiedDataDirectory); - // Exception Message: Could not split contents. We expected every line to contains more than 4 elements, but it has only 3 elements. - Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + // Expected every line to contain more than 4 elements, but it has only 3 elements. + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseMethodName("2").UseDirectory(VerifiedDataDirectory); string tcpFile = " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + " 0: 030011AC:8AF2 C1B17822:01BB 00 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n"; string tcp6File = " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 " + - "12 00000000:00000000 00:00000000 00000000 " + - "472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n"; + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 12 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n"; fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp"), tcpFile); fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); // Cannot find status: 00 in LINUX_TCP_STATE - Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseMethodName("3").UseDirectory(VerifiedDataDirectory); // Cannot find status: 12 in LINUX_TCP_STATE - Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv6StateInfo)).UseMethodName("4").UseDirectory(VerifiedDataDirectory); tcpFile = ""; tcp6File = ""; @@ -75,17 +77,17 @@ public void Parser_Tcp_State_Info_Exception() fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); // Could not parse '/proc/net/tcp'. File was empty. - Assert.Throws(() => parser.GetTcpIPv4StateInfo()); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseMethodName("5").UseDirectory(VerifiedDataDirectory); // Could not parse '/proc/net/tcp6'. File was empty. - Assert.Throws(() => parser.GetTcpIPv6StateInfo()); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv6StateInfo)).UseMethodName("6").UseDirectory(VerifiedDataDirectory); } [Fact] public void Parser_Tcp_State_Info() { var parser = new LinuxNetworkUtilizationParser(new FileNamesOnlyFileSystem(TestResources.TestFilesLocation)); - var tcp4StateInfo = parser.GetTcpIPv4StateInfo(); + TcpStateInfo tcp4StateInfo = parser.GetTcpIPv4StateInfo(); Assert.Equal(2, tcp4StateInfo.EstabCount); Assert.Equal(1, tcp4StateInfo.SynSentCount); @@ -112,5 +114,4 @@ public void Parser_Tcp_State_Info() Assert.Equal(1, tcp6StateInfo.ListenCount); Assert.Equal(1, tcp6StateInfo.ClosingCount); } - } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs index a9e0fb0fbc3..41dee36f63c 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/FileNamesOnlyFileSystem.cs @@ -58,11 +58,11 @@ public int Read(FileInfo file, int length, Span destination) public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) { - var a = File.ReadAllLines($"{_directory}/{file.Name}"); - foreach (var item in a) + string[] lines = File.ReadAllLines($"{_directory}/{file.Name}"); + foreach (var line in lines) { destination.Reset(); - destination.Write(item); + destination.Write(line); yield return destination.WrittenMemory; } } diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs index ac83153ceeb..0ca972cd30c 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Resources/HardcodedValueFileSystem.cs @@ -94,7 +94,7 @@ public void ReplaceFileContent(FileInfo file, string value) public IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination) { - var flag = !_fileContent.TryGetValue(file.FullName, out var content); + bool flag = !_fileContent.TryGetValue(file.FullName, out var content); if (_fileContent.Count == 0 || flag) { destination.Reset(); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.verified.txt new file mode 100644 index 00000000000..18fd7dd650a --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.verified.txt @@ -0,0 +1,10 @@ +{ + Type: InvalidOperationException, + Message: Could not split contents. We expected every line to contain more than 4 elements, but it has only 2 elements., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.verified.txt new file mode 100644 index 00000000000..18fd7dd650a --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.verified.txt @@ -0,0 +1,10 @@ +{ + Type: InvalidOperationException, + Message: Could not split contents. We expected every line to contain more than 4 elements, but it has only 2 elements., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt new file mode 100644 index 00000000000..d221be2178e --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidEnumArgumentException, + Message: Cannot find status: 00 in LinuxTcpState, + StackTrace: +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt new file mode 100644 index 00000000000..b2bf541ce52 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidEnumArgumentException, + Message: Cannot find status: 12 in LinuxTcpState, + StackTrace: +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv6StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.5.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.5.verified.txt new file mode 100644 index 00000000000..d05022da97a --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.5.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. File was empty., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.6.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.6.verified.txt new file mode 100644 index 00000000000..745bbbf1397 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.6.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp6'. File was empty., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv6StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt new file mode 100644 index 00000000000..5f282702bb0 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt new file mode 100644 index 00000000000..5f282702bb0 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt new file mode 100644 index 00000000000..5f282702bb0 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt new file mode 100644 index 00000000000..5f282702bb0 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt new file mode 100644 index 00000000000..247dbeafcb1 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was 'DFIJEUWGHFWGBWEFWOMDOWKSLA' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt new file mode 100644 index 00000000000..5f282702bb0 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line= .verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line= .verified.txt new file mode 100644 index 00000000000..715f6e120fd --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line= .verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=!@#!$%!@.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=!@#!$%!@.verified.txt new file mode 100644 index 00000000000..f48b75ef20b --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=!@#!$%!@.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '!@#!$%!@' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=.verified.txt new file mode 100644 index 00000000000..715f6e120fd --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt new file mode 100644 index 00000000000..247dbeafcb1 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was 'DFIJEUWGHFWGBWEFWOMDOWKSLA' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=________________________Asdasdasdas dd.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=________________________Asdasdasdas dd.verified.txt new file mode 100644 index 00000000000..20ac0d91b3d --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv4_line=________________________Asdasdasdas dd.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '________________________Asdasdasdas dd' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line= .verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line= .verified.txt new file mode 100644 index 00000000000..715f6e120fd --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line= .verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=!@#!$%!@.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=!@#!$%!@.verified.txt new file mode 100644 index 00000000000..f48b75ef20b --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=!@#!$%!@.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '!@#!$%!@' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=.verified.txt new file mode 100644 index 00000000000..715f6e120fd --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt new file mode 100644 index 00000000000..247dbeafcb1 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=DFIJEUWGHFWGBWEFWOMDOWKSLA.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was 'DFIJEUWGHFWGBWEFWOMDOWKSLA' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=________________________Asdasdasdas dd.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=________________________Asdasdasdas dd.verified.txt new file mode 100644 index 00000000000..20ac0d91b3d --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.ipv6_line=________________________Asdasdasdas dd.verified.txt @@ -0,0 +1,9 @@ +{ + Type: InvalidOperationException, + Message: Could not parse '/proc/net/tcp'. We expected first line of the file to start with 'sl' but it was '________________________Asdasdasdas dd' instead., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file From 7d01fc4c4c6c729fdb4cfd48418d2ff3e763ccd8 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:33:49 +0200 Subject: [PATCH 04/13] Fix tests --- src/Shared/BufferWriterPool/BufferWriter.cs | 2 ++ src/Shared/RentedSpan/RentedSpan.cs | 2 ++ src/Shared/StringSplit/StringRange.cs | 2 ++ .../Linux/LinuxNetworkUtilizationParserTests.cs | 4 ++-- ...workUtilizationParserTests.1.DotNet6_0.verified.txt | 10 ++++++++++ ...orkUtilizationParserTests.1.DotNet8_0.verified.txt} | 0 ...workUtilizationParserTests.2.DotNet6_0.verified.txt | 10 ++++++++++ ...orkUtilizationParserTests.2.DotNet8_0.verified.txt} | 0 8 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.DotNet6_0.verified.txt rename test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/{LinuxNetworkUtilizationParserTests.1.verified.txt => LinuxNetworkUtilizationParserTests.1.DotNet8_0.verified.txt} (100%) create mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.DotNet6_0.verified.txt rename test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/{LinuxNetworkUtilizationParserTests.2.verified.txt => LinuxNetworkUtilizationParserTests.2.DotNet8_0.verified.txt} (100%) diff --git a/src/Shared/BufferWriterPool/BufferWriter.cs b/src/Shared/BufferWriterPool/BufferWriter.cs index f8dc2b7c42a..18269616f49 100644 --- a/src/Shared/BufferWriterPool/BufferWriter.cs +++ b/src/Shared/BufferWriterPool/BufferWriter.cs @@ -8,7 +8,9 @@ using System.Runtime.CompilerServices; #endif +#pragma warning disable CA1716 namespace Microsoft.Shared.Pools; +#pragma warning restore CA1716 /// /// Represents an output sink into which data can be written. diff --git a/src/Shared/RentedSpan/RentedSpan.cs b/src/Shared/RentedSpan/RentedSpan.cs index efebc2e2ffe..c7b429b0b67 100644 --- a/src/Shared/RentedSpan/RentedSpan.cs +++ b/src/Shared/RentedSpan/RentedSpan.cs @@ -5,7 +5,9 @@ using System.Buffers; using System.Runtime.CompilerServices; +#pragma warning disable CA1716 namespace Microsoft.Shared.Pools; +#pragma warning restore CA1716 /// /// Represents a span that's potentially created over a rented array. diff --git a/src/Shared/StringSplit/StringRange.cs b/src/Shared/StringSplit/StringRange.cs index 2acd9317f77..e120dc3a869 100644 --- a/src/Shared/StringSplit/StringRange.cs +++ b/src/Shared/StringSplit/StringRange.cs @@ -6,7 +6,9 @@ using System; using Microsoft.Shared.Diagnostics; +#pragma warning disable CA1716 namespace Microsoft.Shared.StringSplit; +#pragma warning restore CA1716 #if !SHARED_PROJECT [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs index 1aaf633b755..71ef94168fd 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs @@ -49,10 +49,10 @@ public async Task Parser_Tcp_State_Info_Exception() var parser = new LinuxNetworkUtilizationParser(fileSystem); // Expected every line to contain more than 4 elements, but it has only 2 elements. - await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseMethodName("1").UseDirectory(VerifiedDataDirectory); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UniqueForRuntimeAndVersion().UseMethodName("1").UseDirectory(VerifiedDataDirectory); // Expected every line to contain more than 4 elements, but it has only 3 elements. - await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UseMethodName("2").UseDirectory(VerifiedDataDirectory); + await Verifier.Verify(Record.Exception(parser.GetTcpIPv4StateInfo)).UniqueForRuntimeAndVersion().UseMethodName("2").UseDirectory(VerifiedDataDirectory); string tcpFile = " sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.DotNet6_0.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.DotNet6_0.verified.txt new file mode 100644 index 00000000000..09d6d8023a5 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.DotNet6_0.verified.txt @@ -0,0 +1,10 @@ +{ + Type: InvalidOperationException, + Message: Could not split contents. We expected every line to contain more than 3 elements, but it has only 2 elements., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.DotNet8_0.verified.txt similarity index 100% rename from test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.verified.txt rename to test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.1.DotNet8_0.verified.txt diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.DotNet6_0.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.DotNet6_0.verified.txt new file mode 100644 index 00000000000..09d6d8023a5 --- /dev/null +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.DotNet6_0.verified.txt @@ -0,0 +1,10 @@ +{ + Type: InvalidOperationException, + Message: Could not split contents. We expected every line to contain more than 3 elements, but it has only 2 elements., + StackTrace: +at Microsoft.Shared.Diagnostics.Throw.InvalidOperationException(String message) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) +at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() +at Xunit.Record.Exception(Func`1 testCode) +} \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.DotNet8_0.verified.txt similarity index 100% rename from test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.verified.txt rename to test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.2.DotNet8_0.verified.txt From 452842fbee14ede21f22e1a36d081d2d94c34ffd Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:37:08 +0200 Subject: [PATCH 05/13] fixes --- src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs | 2 ++ src/Shared/Data.Validation/TimeSpanAttribute.cs | 2 ++ src/Shared/Debugger/DebuggerExtensions.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs b/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs index d11d41a6930..50533f74b2b 100644 --- a/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs +++ b/src/Shared/BufferWriterPool/BufferWriterPooledObjectPolicy.cs @@ -4,7 +4,9 @@ using Microsoft.Extensions.ObjectPool; using Microsoft.Shared.Diagnostics; +#pragma warning disable CA1716 namespace Microsoft.Shared.Pools; +#pragma warning restore CA1716 /// /// An object pooling policy designed for . diff --git a/src/Shared/Data.Validation/TimeSpanAttribute.cs b/src/Shared/Data.Validation/TimeSpanAttribute.cs index bc181e645cf..8830a4120e5 100644 --- a/src/Shared/Data.Validation/TimeSpanAttribute.cs +++ b/src/Shared/Data.Validation/TimeSpanAttribute.cs @@ -7,7 +7,9 @@ using System.Globalization; using Microsoft.Shared.Diagnostics; +#pragma warning disable CA1716 namespace Microsoft.Shared.Data.Validation; +#pragma warning restore CA1716 /// /// Provides boundary validation for . diff --git a/src/Shared/Debugger/DebuggerExtensions.cs b/src/Shared/Debugger/DebuggerExtensions.cs index 4aa63ee590a..cc197be12c2 100644 --- a/src/Shared/Debugger/DebuggerExtensions.cs +++ b/src/Shared/Debugger/DebuggerExtensions.cs @@ -5,7 +5,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Throw = Microsoft.Shared.Diagnostics.Throw; +#pragma warning disable CA1716 namespace Microsoft.Shared.Diagnostics; +#pragma warning restore CA1716 /// /// Adds debugger to DI container. From 83f99e4b5a8bbb304cdad9484b5709bef5425d14 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:50:30 +0200 Subject: [PATCH 06/13] fix warnings --- .../Linux/Network/LinuxNetworkUtilizationParser.cs | 2 +- .../Linux/Network/LinuxTcpTableInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index f622133eed9..29e6c42ad7f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -13,7 +13,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; -internal class LinuxNetworkUtilizationParser +internal sealed class LinuxNetworkUtilizationParser { private static readonly ObjectPool> _sharedBufferWriterPool = BufferWriterPool.CreateBufferWriterPool(); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs index 5ca031ff440..105c8bec9b5 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; -internal class LinuxTcpTableInfo : ITcpTableInfo +internal sealed class LinuxTcpTableInfo : ITcpTableInfo { private readonly object _lock = new(); private readonly TimeSpan _samplingInterval; From 5ae321cc53284dafdbead3f5f493f6ea39583267 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:08:38 +0200 Subject: [PATCH 07/13] PR Comments --- ...pTableInfo.cs => ITcpStateInfoProvider.cs} | 12 ++-- .../Linux/IFileSystem.cs | 2 +- .../Linux/Network/LinuxNetworkMetrics.cs | 67 +++++++++-------- .../Network/LinuxNetworkUtilizationParser.cs | 17 ++--- ...uxTcpTableInfo.cs => LinuxTcpStateInfo.cs} | 8 +-- ...ions.Diagnostics.ResourceMonitoring.csproj | 1 - ...ceMonitoringServiceCollectionExtensions.cs | 4 +- .../Windows/Network/WindowsNetworkMetrics.cs | 71 +++++++++---------- ...TcpTableInfo.cs => WindowsTcpStateInfo.cs} | 8 +-- .../Linux/LinuxCountersTests.cs | 4 +- .../LinuxNetworkUtilizationParserTests.cs | 2 +- ...tworkUtilizationParserTests.3.verified.txt | 6 +- ...tworkUtilizationParserTests.4.verified.txt | 6 +- ...ceMonitoringOptionsCustomValidatorTests.cs | 2 +- .../Windows/Tcp6TableInfoTests.cs | 16 ++--- .../Windows/TcpTableInfoTests.cs | 18 ++--- ...apshotProvider_EmitsLogRecord.verified.txt | 68 ------------------ .../Windows/WindowsCountersTests.cs | 4 +- 18 files changed, 123 insertions(+), 193 deletions(-) rename src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/{ITcpTableInfo.cs => ITcpStateInfoProvider.cs} (57%) rename src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/{LinuxTcpTableInfo.cs => LinuxTcpStateInfo.cs} (85%) rename src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/{WindowsTcpTableInfo.cs => WindowsTcpStateInfo.cs} (97%) delete mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.verified.txt diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs similarity index 57% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs index 90ec8c5664f..c7c0d999330 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpTableInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs @@ -4,19 +4,19 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; /// -/// An interface for getting TCP/IP table information. +/// An interface for getting TCP/IP state information. /// -internal interface ITcpTableInfo +internal interface ITcpStateInfoProvider { /// - /// Gets the last known snapshot of TCP/IP v4 state info on the system. + /// Gets the last known information about TCP/IP v4 state on the system. /// /// An instance of . - TcpStateInfo GetIpV4CachingSnapshot(); + TcpStateInfo GetpIpV4TcpStateInfo(); /// - /// Gets the last known snapshot of TCP/IP v6 state info on the system. + /// Gets the last known information about TCP/IP v6 state on the system. /// /// An instance of . - TcpStateInfo GetIpV6CachingSnapshot(); + TcpStateInfo GetpIpV6TcpStateInfo(); } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs index 2cb34d777ca..80e185a5e70 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs @@ -16,7 +16,7 @@ internal interface IFileSystem /// /// Checks for file existence. /// - /// or . + /// if file exists; otherwise, . bool Exists(FileInfo fileInfo); /// diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs index 07c71fa039c..4476a7fc01f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs @@ -9,11 +9,11 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; internal sealed class LinuxNetworkMetrics { - private readonly ITcpTableInfo _tcpTableInfo; + private readonly ITcpStateInfoProvider _tcpStateInfoProvider; - public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpTableInfo tcpTableInfo) + public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider tcpStateInfoProvider) { - _tcpTableInfo = tcpTableInfo; + _tcpStateInfoProvider = tcpStateInfoProvider; #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that @@ -22,11 +22,8 @@ public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpTableInfo tcpTableInf var meter = meterFactory.Create(nameof(ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope - var tcpTag = new KeyValuePair("network.transport", "tcp"); - var commonTags = new TagList - { - tcpTag - }; + KeyValuePair tcpTag = new("network.transport", "tcp"); + TagList commonTags = new() { tcpTag }; // The metric is aligned with // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemnetworkconnections @@ -43,38 +40,38 @@ private IEnumerable> GetMeasurements() const string NetworkStateKey = "system.network.state"; // These are covered in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes: - var tcpVersionFourTag = new KeyValuePair("network.type", "ipv4"); - var tcpVersionSixTag = new KeyValuePair("network.type", "ipv6"); + KeyValuePair tcpVersionFourTag = new("network.type", "ipv4"); + KeyValuePair tcpVersionSixTag = new("network.type", "ipv6"); - var measurements = new List>(24); + List> measurements = new(24); // IPv4: - TcpStateInfo snapshotV4 = _tcpTableInfo.GetIpV4CachingSnapshot(); - measurements.Add(new Measurement(snapshotV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); - measurements.Add(new Measurement(snapshotV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); - measurements.Add(new Measurement(snapshotV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); - measurements.Add(new Measurement(snapshotV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); - measurements.Add(new Measurement(snapshotV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); - measurements.Add(new Measurement(snapshotV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); - measurements.Add(new Measurement(snapshotV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); - measurements.Add(new Measurement(snapshotV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); - measurements.Add(new Measurement(snapshotV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); - measurements.Add(new Measurement(snapshotV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); - measurements.Add(new Measurement(snapshotV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); + TcpStateInfo stateV4 = _tcpStateInfoProvider.GetpIpV4TcpStateInfo(); + measurements.Add(new Measurement(stateV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(stateV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(stateV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(stateV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(stateV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(stateV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(stateV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(stateV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(stateV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(stateV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(stateV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); // IPv6: - TcpStateInfo snapshotV6 = _tcpTableInfo.GetIpV6CachingSnapshot(); - measurements.Add(new Measurement(snapshotV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); - measurements.Add(new Measurement(snapshotV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); - measurements.Add(new Measurement(snapshotV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); - measurements.Add(new Measurement(snapshotV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); - measurements.Add(new Measurement(snapshotV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); - measurements.Add(new Measurement(snapshotV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); - measurements.Add(new Measurement(snapshotV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); - measurements.Add(new Measurement(snapshotV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); - measurements.Add(new Measurement(snapshotV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); - measurements.Add(new Measurement(snapshotV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); - measurements.Add(new Measurement(snapshotV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); + TcpStateInfo stateV6 = _tcpStateInfoProvider.GetpIpV6TcpStateInfo(); + measurements.Add(new Measurement(stateV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(stateV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(stateV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(stateV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(stateV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(stateV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(stateV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(stateV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(stateV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(stateV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(stateV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); return measurements; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index 29e6c42ad7f..5fb2379d89f 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.ComponentModel; using System.IO; using Microsoft.Extensions.ObjectPool; #if !NET8_0_OR_GREATER @@ -45,8 +44,8 @@ public LinuxNetworkUtilizationParser(IFileSystem fileSystem) } /// - /// The method is used to read Span data and calculate the TCP state info. - /// Refer proc net tcp. + /// Parses the contents of the and updates the with the parsed information. + /// For the data format expected in the , refer proc net tcp. /// private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo tcpStateInfo) { @@ -82,8 +81,9 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t #endif // until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 - // we have to allocate & throw away memory using ToString(): - switch ((LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), Base16)) + // we have to allocate & throw away memory using .ToString(): + var state = (LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), Base16); + switch (state) { case LinuxTcpState.ESTABLISHED: tcpStateInfo.EstabCount++; @@ -119,7 +119,8 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t tcpStateInfo.ClosingCount++; break; default: - throw new InvalidEnumArgumentException($"Cannot find status: {tcpConnectionState} in LinuxTcpState"); + Throw.IfOutOfRange(state); + break; } } @@ -130,7 +131,7 @@ private TcpStateInfo GetTcpStateInfo(FileInfo file) { // The value we are interested in starts with this "sl". const string Sl = "sl"; - var tcpStateInfo = new TcpStateInfo(); + TcpStateInfo tcpStateInfo = new(); using ReturnableBufferWriter bufferWriter = new(_sharedBufferWriterPool); using var enumerableLines = _fileSystem.ReadAllByLines(file, bufferWriter.Buffer).GetEnumerator(); if (!enumerableLines.MoveNext()) @@ -138,7 +139,7 @@ private TcpStateInfo GetTcpStateInfo(FileInfo file) Throw.InvalidOperationException($"Could not parse '{file}'. File was empty."); } - var firstLine = enumerableLines.Current.TrimStart().Span; + ReadOnlySpan firstLine = enumerableLines.Current.TrimStart().Span; if (!firstLine.StartsWith(Sl, StringComparison.Ordinal)) { Throw.InvalidOperationException($"Could not parse '{file}'. We expected first line of the file to start with '{Sl}' but it was '{firstLine.ToString()}' instead."); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs similarity index 85% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs index 105c8bec9b5..390bcda64ea 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpTableInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxTcpStateInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network; -internal sealed class LinuxTcpTableInfo : ITcpTableInfo +internal sealed class LinuxTcpStateInfo : ITcpStateInfoProvider { private readonly object _lock = new(); private readonly TimeSpan _samplingInterval; @@ -17,19 +17,19 @@ internal sealed class LinuxTcpTableInfo : ITcpTableInfo private TcpStateInfo _iPv6Snapshot = new(); private DateTimeOffset _refreshAfter; - public LinuxTcpTableInfo(IOptions options, LinuxNetworkUtilizationParser parser) + public LinuxTcpStateInfo(IOptions options, LinuxNetworkUtilizationParser parser) { _samplingInterval = options.Value.SamplingInterval; _parser = parser; } - public TcpStateInfo GetIpV4CachingSnapshot() + public TcpStateInfo GetpIpV4TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv4Snapshot; } - public TcpStateInfo GetIpV6CachingSnapshot() + public TcpStateInfo GetpIpV6TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv6Snapshot; diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj index a98c18191be..c8658a5945d 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Microsoft.Extensions.Diagnostics.ResourceMonitoring.csproj @@ -45,7 +45,6 @@ - diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs index 6e1343aa9b6..d145f718405 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ResourceMonitoringServiceCollectionExtensions.cs @@ -91,7 +91,7 @@ private static ResourceMonitorBuilder AddWindowsProvider(this ResourceMonitorBui _ = builder.Services .AddActivatedSingleton() - .AddActivatedSingleton(); + .AddActivatedSingleton(); return builder; } @@ -123,7 +123,7 @@ private static ResourceMonitorBuilder AddLinuxProvider(this ResourceMonitorBuild _ = builder.Services .AddActivatedSingleton() .AddActivatedSingleton() - .AddActivatedSingleton(); + .AddActivatedSingleton(); return builder; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs index 810eba9c2a2..60332880cd7 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs @@ -9,11 +9,11 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; internal sealed class WindowsNetworkMetrics { - private readonly ITcpTableInfo _tcpTableInfo; + private readonly ITcpStateInfoProvider _tcpStateInfoProvider; - public WindowsNetworkMetrics(IMeterFactory meterFactory, ITcpTableInfo tcpTableInfo) + public WindowsNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider tcpStateInfoProvider) { - _tcpTableInfo = tcpTableInfo; + _tcpStateInfoProvider = tcpStateInfoProvider; #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that @@ -22,11 +22,8 @@ public WindowsNetworkMetrics(IMeterFactory meterFactory, ITcpTableInfo tcpTableI var meter = meterFactory.Create(nameof(ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope - var tcpTag = new KeyValuePair("network.transport", "tcp"); - var commonTags = new TagList - { - tcpTag - }; + KeyValuePair tcpTag = new("network.transport", "tcp"); + TagList commonTags = new() { tcpTag }; // The metric is aligned with // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/system/system-metrics.md#metric-systemnetworkconnections @@ -43,40 +40,40 @@ private IEnumerable> GetMeasurements() const string NetworkStateKey = "system.network.state"; // These are covered in https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes: - var tcpVersionFourTag = new KeyValuePair("network.type", "ipv4"); - var tcpVersionSixTag = new KeyValuePair("network.type", "ipv6"); + KeyValuePair tcpVersionFourTag = new("network.type", "ipv4"); + KeyValuePair tcpVersionSixTag = new("network.type", "ipv6"); - var measurements = new List>(24); + List> measurements = new(24); // IPv4: - var snapshotV4 = _tcpTableInfo.GetIpV4CachingSnapshot(); - measurements.Add(new Measurement(snapshotV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); - measurements.Add(new Measurement(snapshotV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); - measurements.Add(new Measurement(snapshotV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); - measurements.Add(new Measurement(snapshotV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); - measurements.Add(new Measurement(snapshotV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); - measurements.Add(new Measurement(snapshotV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); - measurements.Add(new Measurement(snapshotV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); - measurements.Add(new Measurement(snapshotV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); - measurements.Add(new Measurement(snapshotV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); - measurements.Add(new Measurement(snapshotV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); - measurements.Add(new Measurement(snapshotV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); - measurements.Add(new Measurement(snapshotV4.DeleteTcbCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "delete") })); + TcpStateInfo stateV4 = _tcpStateInfoProvider.GetpIpV4TcpStateInfo(); + measurements.Add(new Measurement(stateV4.ClosedCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(stateV4.ListenCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(stateV4.SynSentCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(stateV4.SynRcvdCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(stateV4.EstabCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(stateV4.FinWait1Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(stateV4.FinWait2Count, new TagList { tcpVersionFourTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(stateV4.CloseWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(stateV4.ClosingCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(stateV4.LastAckCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(stateV4.TimeWaitCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "time_wait") })); + measurements.Add(new Measurement(stateV4.DeleteTcbCount, new TagList { tcpVersionFourTag, new(NetworkStateKey, "delete") })); // IPv6: - var snapshotV6 = _tcpTableInfo.GetIpV6CachingSnapshot(); - measurements.Add(new Measurement(snapshotV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); - measurements.Add(new Measurement(snapshotV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); - measurements.Add(new Measurement(snapshotV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); - measurements.Add(new Measurement(snapshotV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); - measurements.Add(new Measurement(snapshotV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); - measurements.Add(new Measurement(snapshotV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); - measurements.Add(new Measurement(snapshotV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); - measurements.Add(new Measurement(snapshotV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); - measurements.Add(new Measurement(snapshotV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); - measurements.Add(new Measurement(snapshotV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); - measurements.Add(new Measurement(snapshotV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); - measurements.Add(new Measurement(snapshotV6.DeleteTcbCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "delete") })); + TcpStateInfo stateV6 = _tcpStateInfoProvider.GetpIpV6TcpStateInfo(); + measurements.Add(new Measurement(stateV6.ClosedCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close") })); + measurements.Add(new Measurement(stateV6.ListenCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "listen") })); + measurements.Add(new Measurement(stateV6.SynSentCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_sent") })); + measurements.Add(new Measurement(stateV6.SynRcvdCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "syn_recv") })); + measurements.Add(new Measurement(stateV6.EstabCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "established") })); + measurements.Add(new Measurement(stateV6.FinWait1Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_1") })); + measurements.Add(new Measurement(stateV6.FinWait2Count, new TagList { tcpVersionSixTag, new(NetworkStateKey, "fin_wait_2") })); + measurements.Add(new Measurement(stateV6.CloseWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "close_wait") })); + measurements.Add(new Measurement(stateV6.ClosingCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "closing") })); + measurements.Add(new Measurement(stateV6.LastAckCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "last_ack") })); + measurements.Add(new Measurement(stateV6.TimeWaitCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "time_wait") })); + measurements.Add(new Measurement(stateV6.DeleteTcbCount, new TagList { tcpVersionSixTag, new(NetworkStateKey, "delete") })); return measurements; } diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpTableInfo.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs similarity index 97% rename from src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpTableInfo.cs rename to src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs index e203f5f0828..732c522cda5 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpTableInfo.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsTcpStateInfo.cs @@ -11,7 +11,7 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.Network; -internal sealed class WindowsTcpTableInfo : ITcpTableInfo +internal sealed class WindowsTcpStateInfo : ITcpStateInfoProvider { private readonly object _lock = new(); private readonly FrozenSet _localIPAddresses; @@ -25,7 +25,7 @@ internal sealed class WindowsTcpTableInfo : ITcpTableInfo private static TimeProvider TimeProvider => TimeProvider.System; private DateTimeOffset _refreshAfter; - public WindowsTcpTableInfo(IOptions options) + public WindowsTcpStateInfo(IOptions options) { var stringAddresses = options.Value.SourceIpAddresses; _localIPAddresses = stringAddresses @@ -42,13 +42,13 @@ public WindowsTcpTableInfo(IOptions options) _refreshAfter = default; } - public TcpStateInfo GetIpV4CachingSnapshot() + public TcpStateInfo GetpIpV4TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv4Snapshot; } - public TcpStateInfo GetIpV6CachingSnapshot() + public TcpStateInfo GetpIpV6TcpStateInfo() { RefreshSnapshotIfNeeded(); return _iPv6Snapshot; diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs index 40ffa7e011d..abe3098661d 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxCountersTests.cs @@ -83,8 +83,8 @@ public void LinuxNetworkCounters_Registers_Instruments() .Setup(x => x.Create(It.IsAny())) .Returns(meter); - var tcpTableInfo = new LinuxTcpTableInfo(options, parser); - var lnm = new LinuxNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); + var tcpStateInfo = new LinuxTcpStateInfo(options, parser); + var lnm = new LinuxNetworkMetrics(meterFactoryMock.Object, tcpStateInfo); using var listener = new MeterListener { diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs index 71ef94168fd..ee3c60597c7 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/LinuxNetworkUtilizationParserTests.cs @@ -59,7 +59,7 @@ public async Task Parser_Tcp_State_Info_Exception() " 0: 030011AC:8AF2 C1B17822:01BB 00 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1\r\n"; string tcp6File = " sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\r\n" + - " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 12 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n"; + " 0: 00000000000000000000000000000000:0BBB 00000000000000000000000000000000:0003 0C 00000000:00000000 00:00000000 00000000 472 0 2455375 1 00000000f4cb7621 100 0 0 10 0\r\n"; fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp"), tcpFile); fileSystem.ReplaceFileContent(new FileInfo("/proc/net/tcp6"), tcp6File); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt index d221be2178e..cccdff9332f 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.3.verified.txt @@ -1,7 +1,9 @@ { - Type: InvalidEnumArgumentException, - Message: Cannot find status: 00 in LinuxTcpState, + Type: ArgumentOutOfRangeException, + Message: 0 is an invalid value for enum type Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxTcpState (Parameter 'state'), + ParamName: state, StackTrace: +at Microsoft.Shared.Diagnostics.Throw.ArgumentOutOfRangeException(String paramName, String message) at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv4StateInfo() diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt index b2bf541ce52..755a57b8e3c 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.4.verified.txt @@ -1,7 +1,9 @@ { - Type: InvalidEnumArgumentException, - Message: Cannot find status: 12 in LinuxTcpState, + Type: ArgumentOutOfRangeException, + Message: 12 is an invalid value for enum type Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxTcpState (Parameter 'state'), + ParamName: state, StackTrace: +at Microsoft.Shared.Diagnostics.Throw.ArgumentOutOfRangeException(String paramName, String message) at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.UpdateTcpStateInfo(ReadOnlySpan`1 buffer, TcpStateInfo tcpStateInfo) at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpStateInfo(FileInfo file) at Microsoft.Extensions.Diagnostics.ResourceMonitoring.Linux.Network.LinuxNetworkUtilizationParser.GetTcpIPv6StateInfo() diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs index 35583f17224..2fabf0f9400 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/ResourceMonitoringOptionsCustomValidatorTests.cs @@ -20,7 +20,7 @@ public void Test_WindowsCountersOptionsCustomValidator_With_Wrong_IP_Address() { SourceIpAddresses = new HashSet { "" } }; - var tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + var tcpStateInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); var validator = new ResourceMonitoringOptionsCustomValidator(); var result = validator.Validate("", options); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs index 5f30cc9d8fd..3ef240e1274 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Tcp6TableInfoTests.cs @@ -234,11 +234,11 @@ public void Test_Tcp6TableInfo_Get_UnsuccessfulStatus_All_The_Time() SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpTableInfo tcp6TableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + WindowsTcpStateInfo tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); + var tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); }); } @@ -250,11 +250,11 @@ public void Test_Tcp6TableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter( SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpTableInfo tcp6TableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + WindowsTcpStateInfo tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); + var tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); }); } @@ -268,9 +268,9 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() SourceIpAddresses = new HashSet { "[::1]" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpTableInfo tcp6TableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + WindowsTcpStateInfo tcp6TableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcp6TableInfo.SetGetTcp6TableDelegate(FakeGetTcp6TableWithFakeInformation); - var tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); + var tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -286,7 +286,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() Assert.Equal(1, tcpStateInfo.DeleteTcbCount); // Second calling in a small interval. - tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); + tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -303,7 +303,7 @@ public void Test_Tcp6TableInfo_Get_Correct_Information() // Third calling in a long interval. Thread.Sleep(6000); - tcpStateInfo = tcp6TableInfo.GetIpV6CachingSnapshot(); + tcpStateInfo = tcp6TableInfo.GetpIpV6TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(2, tcpStateInfo.ClosedCount); Assert.Equal(2, tcpStateInfo.ListenCount); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs index 8f6309085ec..fb79d0cb839 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/TcpTableInfoTests.cs @@ -177,11 +177,11 @@ public void Test_TcpTableInfo_Get_UnsuccessfulStatus_All_The_Time() SourceIpAddresses = new HashSet { "127.0.0.1" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpTableInfo tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + WindowsTcpStateInfo tcpTableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithUnsuccessfulStatusAllTheTime); Assert.Throws(() => { - var tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); + var tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); }); } @@ -193,11 +193,11 @@ public void Test_TcpTableInfo_Get_InsufficientBuffer_Then_Get_InvalidParameter() SourceIpAddresses = new HashSet { "127.0.0.1" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpTableInfo tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + WindowsTcpStateInfo tcpTableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithInsufficientBufferAndInvalidParameter); Assert.Throws(() => { - var tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); + var tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); }); } @@ -211,9 +211,9 @@ public void Test_TcpTableInfo_Get_Correct_Information() SourceIpAddresses = new HashSet { "127.0.0.1" }, SamplingInterval = DefaultTimeSpan }; - WindowsTcpTableInfo tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + WindowsTcpStateInfo tcpTableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(FakeGetTcpTableWithFakeInformation); - var tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); + var tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -229,7 +229,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() Assert.Equal(1, tcpStateInfo.DeleteTcbCount); // Second calling in a small interval. - tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); + tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(1, tcpStateInfo.ClosedCount); Assert.Equal(1, tcpStateInfo.ListenCount); @@ -246,7 +246,7 @@ public void Test_TcpTableInfo_Get_Correct_Information() // Third calling in a long interval. Thread.Sleep(6000); - tcpStateInfo = tcpTableInfo.GetIpV4CachingSnapshot(); + tcpStateInfo = tcpTableInfo.GetpIpV4TcpStateInfo(); Assert.NotNull(tcpStateInfo); Assert.Equal(2, tcpStateInfo.ClosedCount); Assert.Equal(2, tcpStateInfo.ListenCount); @@ -268,7 +268,7 @@ public void Test_TcpTableInfo_CalculateCount_default_branch() TcpStateInfo tcpStateInfo = new(); // Add this case to increase coverage, but 0 will not happen in actual case. - WindowsTcpTableInfo.CalculateCount(tcpStateInfo, 0); + WindowsTcpStateInfo.CalculateCount(tcpStateInfo, 0); Assert.NotNull(tcpStateInfo); Assert.Equal(0, tcpStateInfo.ClosedCount); Assert.Equal(0, tcpStateInfo.ListenCount); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.verified.txt deleted file mode 100644 index 228dccfb1c4..00000000000 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/Verified/WindowsContainerSnapshotProviderTests.SnapshotProvider_EmitsLogRecord.verified.txt +++ /dev/null @@ -1,68 +0,0 @@ -[ - { - Level: Information, - Id: { - Id: 1, - Name: RunningInsideJobObject - }, - State: [ - { - {OriginalFormat}: Resource Monitoring is running inside a Job Object. For more information about Job Objects see https://aka.ms/job-objects - } - ], - StructuredState: [ - { - {OriginalFormat}: Resource Monitoring is running inside a Job Object. For more information about Job Objects see https://aka.ms/job-objects - } - ], - Message: Resource Monitoring is running inside a Job Object. For more information about Job Objects see https://aka.ms/job-objects, - Category: Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.WindowsContainerSnapshotProvider, - LevelEnabled: true, - Timestamp: DateTimeOffset_1 - }, - { - Level: Debug, - Id: { - Id: 6, - Name: SystemResourcesInfo - }, - State: [ - { - {OriginalFormat}: System resources information: CpuLimit = {cpuLimit}, CpuRequest = {cpuRequest}, MemoryLimit = {memoryLimit}, MemoryRequest = {memoryRequest}. - }, - { - memoryRequest: 2000 - }, - { - memoryLimit: 2000 - }, - { - cpuRequest: 1 - }, - { - cpuLimit: 1 - } - ], - StructuredState: [ - { - {OriginalFormat}: System resources information: CpuLimit = {cpuLimit}, CpuRequest = {cpuRequest}, MemoryLimit = {memoryLimit}, MemoryRequest = {memoryRequest}. - }, - { - memoryRequest: 2000 - }, - { - memoryLimit: 2000 - }, - { - cpuRequest: 1 - }, - { - cpuLimit: 1 - } - ], - Message: System resources information: CpuLimit = 1, CpuRequest = 1, MemoryLimit = 2000, MemoryRequest = 2000., - Category: Microsoft.Extensions.Diagnostics.ResourceMonitoring.Windows.WindowsContainerSnapshotProvider, - LevelEnabled: true, - Timestamp: DateTimeOffset_2 - } -] \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs index 7eb1fdec01e..4ef7d2c4038 100644 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs +++ b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Windows/WindowsCountersTests.cs @@ -32,7 +32,7 @@ public void WindowsCounters_Registers_Instruments() meterFactoryMock.Setup(x => x.Create(It.IsAny())) .Returns(meter); - var tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + var tcpTableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(TcpTableInfoTests.FakeGetTcpTableWithFakeInformation); tcpTableInfo.SetGetTcp6TableDelegate(Tcp6TableInfoTests.FakeGetTcp6TableWithFakeInformation); var windowsCounters = new WindowsNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); @@ -74,7 +74,7 @@ public void WindowsCounters_Got_Unsuccessful() meterFactoryMock.Setup(x => x.Create(It.IsAny())) .Returns(meter); - var tcpTableInfo = new WindowsTcpTableInfo(Options.Options.Create(options)); + var tcpTableInfo = new WindowsTcpStateInfo(Options.Options.Create(options)); tcpTableInfo.SetGetTcpTableDelegate(TcpTableInfoTests.FakeGetTcpTableWithUnsuccessfulStatusAllTheTime); tcpTableInfo.SetGetTcp6TableDelegate(Tcp6TableInfoTests.FakeGetTcp6TableWithUnsuccessfulStatusAllTheTime); var windowsCounters = new WindowsNetworkMetrics(meterFactoryMock.Object, tcpTableInfo); From 5adc7fd9544bd4c2f439c48c524d1dbbe7ce3a1e Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:42:56 +0200 Subject: [PATCH 08/13] PR comments --- .../ITcpStateInfoProvider.cs | 4 ++-- .../Linux/Network/LinuxNetworkUtilizationParser.cs | 9 +++++---- ...Tests.Parser_Throws_When_Data_Is_Invalid.verified.txt | 1 - ...arser_Throws_When_Data_Is_Invalid_line= .verified.txt | 1 - ...hrows_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt | 1 - ...Parser_Throws_When_Data_Is_Invalid_line=.verified.txt | 1 - ..._________________Asdasdasdas dd.verified.txt | 1 - 7 files changed, 7 insertions(+), 11 deletions(-) delete mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt delete mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt delete mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt delete mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt delete mode 100644 test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs index c7c0d999330..0c51e5fc45a 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/ITcpStateInfoProvider.cs @@ -9,13 +9,13 @@ namespace Microsoft.Extensions.Diagnostics.ResourceMonitoring; internal interface ITcpStateInfoProvider { /// - /// Gets the last known information about TCP/IP v4 state on the system. + /// Gets the last known TCP/IP v4 state of the system. /// /// An instance of . TcpStateInfo GetpIpV4TcpStateInfo(); /// - /// Gets the last known information about TCP/IP v6 state on the system. + /// Gets the last known TCP/IP v6 state of the system. /// /// An instance of . TcpStateInfo GetpIpV6TcpStateInfo(); diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index 5fb2379d89f..a08b2fd5c3e 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; using Microsoft.Extensions.ObjectPool; #if !NET8_0_OR_GREATER @@ -29,12 +30,12 @@ internal sealed class LinuxNetworkUtilizationParser private readonly IFileSystem _fileSystem; /// - /// Reads the contents of a file located at _tcp4 and parses it to extract information about the TCP/IP state info on the system. + /// Reads the contents of a file located at and parses it to extract information about the TCP/IP state info of the system. /// public TcpStateInfo GetTcpIPv4StateInfo() => GetTcpStateInfo(_tcp); /// - /// Reads the contents of a file located at _tcp6 and parses it to extract information about the TCP/IP state info on the system. + /// Reads the contents of a file located at and parses it to extract information about the TCP/IP state info of the system. /// public TcpStateInfo GetTcpIPv6StateInfo() => GetTcpStateInfo(_tcp6); @@ -125,7 +126,7 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t } /// - /// Reads the contents of a file and parses it to extract information about the TCP/IP state info on the system. + /// Reads the contents of a file and parses it to extract information about the TCP/IP state info of the system. /// private TcpStateInfo GetTcpStateInfo(FileInfo file) { @@ -133,7 +134,7 @@ private TcpStateInfo GetTcpStateInfo(FileInfo file) const string Sl = "sl"; TcpStateInfo tcpStateInfo = new(); using ReturnableBufferWriter bufferWriter = new(_sharedBufferWriterPool); - using var enumerableLines = _fileSystem.ReadAllByLines(file, bufferWriter.Buffer).GetEnumerator(); + using IEnumerator> enumerableLines = _fileSystem.ReadAllByLines(file, bufferWriter.Buffer).GetEnumerator(); if (!enumerableLines.MoveNext()) { Throw.InvalidOperationException($"Could not parse '{file}'. File was empty."); diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt deleted file mode 100644 index 5f282702bb0..00000000000 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt deleted file mode 100644 index 5f282702bb0..00000000000 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line= .verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt deleted file mode 100644 index 5f282702bb0..00000000000 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=!@#!$%!@.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt deleted file mode 100644 index 5f282702bb0..00000000000 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt b/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt deleted file mode 100644 index 5f282702bb0..00000000000 --- a/test/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring.Tests/Linux/Verified/LinuxNetworkUtilizationParserTests.Parser_Throws_When_Data_Is_Invalid_line=________________________Asdasdasdas dd.verified.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 8c20051acb9399e76fb0ed2df832393c74708f04 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:58:42 +0200 Subject: [PATCH 09/13] PR comments --- .../Linux/Network/LinuxNetworkUtilizationParser.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index a08b2fd5c3e..39e0be31155 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -51,6 +51,11 @@ public LinuxNetworkUtilizationParser(IFileSystem fileSystem) private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo tcpStateInfo) { const int Base16 = 16; + + // The buffer contains one line from /proc/net/tcp(6) file, e.g.: + // 0: 030011AC:8AF2 C1B17822:01BB 01 00000000:00000000 02:000000D1 00000000 472 0 2481276 2 00000000c62511cb 28 4 26 10 -1 + // The line may contain leading spaces, so we have to trim those. + // tcpConnectionState is in the 4th column - i.e., "01". ReadOnlySpan line = buffer.TrimStart(); #if NET8_0_OR_GREATER @@ -81,8 +86,10 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 1].Index, range[Target - 1].Count); #endif - // until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 - // we have to allocate & throw away memory using .ToString(): + // at this point, tcpConnectionState contains one of TCP connection states in hexadecimal format, e.g., "01", + // which we now need to convert to the LinuxTcpState enum. + // note: until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 + // we have to allocate & throw away memory using .ToString() var state = (LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), Base16); switch (state) { From 149803b24922a09ef5d09b3a006dcf26477fbf75 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:45:53 +0200 Subject: [PATCH 10/13] add try/finally --- .../Linux/OSFileSystem.cs | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs index 010dea4c9f0..d9e76c0aa4c 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/OSFileSystem.cs @@ -56,43 +56,48 @@ public IEnumerable> ReadAllByLines(FileInfo file, BufferWri throw new FileNotFoundException(); } - using FileStream stream = file.OpenRead(); - Memory buffer = ArrayPool.Shared.Rent(MaxStackalloc); - int read = stream.Read(buffer.Span); - while (read > 0) + try { - var start = 0; - var end = 0; + using FileStream stream = file.OpenRead(); - for (end = 0; end < read; end++) + int read = stream.Read(buffer.Span); + while (read > 0) { - if (buffer.Span[end] == (byte)'\n') + var start = 0; + var end = 0; + + for (end = 0; end < read; end++) + { + if (buffer.Span[end] == (byte)'\n') + { + var length = end - start; + _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length)); + destination.Advance(length); + start = end + 1; + yield return destination.WrittenMemory; + destination.Reset(); + } + } + + // Set the comparison in the while loop to end when the file has not been completely read into the buffer. + // It will then advance the last character to the destination for the next time yield return is called. + if (start < read) { - var length = end - start; + var length = read - start; _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length)); destination.Advance(length); - start = end + 1; - yield return destination.WrittenMemory; - destination.Reset(); } - } - // Set the comparison in the while loop to end when the file has not been completely read into the buffer. - // It will then advance the last character to the destination for the next time yield return is called. - if (start < read) - { - var length = read - start; - _ = Encoding.ASCII.GetChars(buffer.Span.Slice(start, length), destination.GetSpan(length)); - destination.Advance(length); + read = stream.Read(buffer.Span); } - - read = stream.Read(buffer.Span); } - - if (MemoryMarshal.TryGetArray(buffer, out ArraySegment arraySegment) && arraySegment.Array != null) + finally { - ArrayPool.Shared.Return(arraySegment.Array); + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment arraySegment) && arraySegment.Array != null) + { + ArrayPool.Shared.Return(arraySegment.Array); + } } } From 53fc489a7876b560441263748f406aea23b21cfa Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:07:44 +0200 Subject: [PATCH 11/13] Update xml comments --- .../Linux/IFileSystem.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs index 80e185a5e70..08cbfcf7e74 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/IFileSystem.cs @@ -20,34 +20,34 @@ internal interface IFileSystem bool Exists(FileInfo fileInfo); /// - /// Get directory names on the filesystem based on the provided pattern. + /// Get directory names on the filesystem based on the specified pattern. /// - /// IReadOnlyCollection. + /// A read-only collection of the paths of directories that match the specified pattern, or an empty read-only collection if no directories are found. IReadOnlyCollection GetDirectoryNames(string directory, string pattern); /// - /// Reads content from the file. + /// Reads content of the given length from a file and writes the data in the destination buffer. /// /// - /// Number of chars written. + /// The total number of bytes read into the destination buffer. /// int Read(FileInfo file, int length, Span destination); /// - /// Read all content from a file. + /// Read all content from a file and writes the data in the destination buffer. /// void ReadAll(FileInfo file, BufferWriter destination); /// - /// Reads first line from the file. + /// Reads first line from the file and writes the data in the destination buffer. /// void ReadFirstLine(FileInfo file, BufferWriter destination); /// - /// Reads all content from a file by line. + /// Reads all content from a file line by line. /// /// - /// IEnumerable. + /// The enumerable that represents all the lines of the file. /// IEnumerable> ReadAllByLines(FileInfo file, BufferWriter destination); } From 2cdcc52a2ac415010228e7a60abe020b86107bfd Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:42:56 +0200 Subject: [PATCH 12/13] PR comments --- .../Linux/Network/LinuxNetworkMetrics.cs | 4 ++-- .../Network/LinuxNetworkUtilizationParser.cs | 14 ++++++------- .../Windows/Network/WindowsNetworkMetrics.cs | 2 +- src/Shared/StringSplit/README.md | 2 +- src/Shared/StringSplit/StringRange.cs | 2 +- .../StringSplit/StringSplitExtensions.cs | 20 +++++++++---------- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs index 4476a7fc01f..b5b60de894a 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkMetrics.cs @@ -17,8 +17,8 @@ public LinuxNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider tcp #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that - // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 - // Related documentation: https://github.com/dotnet/docs/pull/37170 + // Is's a false-positive, see: https://github.com/dotnet/roslyn-analyzers/issues/6912 + // Related documentation: https://github.com/dotnet/docs/pull/37170. var meter = meterFactory.Create(nameof(ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs index 39e0be31155..3b9860b31a7 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Linux/Network/LinuxNetworkUtilizationParser.cs @@ -62,17 +62,17 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t const int Target = 5; Span range = stackalloc Range[Target]; - // on .NET 8+, if capacity of destination range array is less than number of ranges found by ReadOnlySpan.Split(), + // In .NET 8+, if capacity of destination range array is less than number of ranges found by ReadOnlySpan.Split(), // the last range in the array will get all the remaining elements of the ReadOnlySpan. - // therefore we request 5 ranges instead of 4, and then range[Target - 2] will have the range we need without the remaining elements. + // Therefore, we request 5 ranges instead of 4, and then range[Target - 2] will have the range we need without the remaining elements. int numRanges = line.Split(range, ' ', StringSplitOptions.RemoveEmptyEntries); #else const int Target = 4; Span range = stackalloc StringRange[Target]; - // in our StringRange API, if capacity of destination range array is less than number of ranges found by ReadOnlySpan.TrySplit(), + // In our StringRange API, if capacity of destination range array is less than number of ranges found by ReadOnlySpan.TrySplit(), // the last range in the array will get the last range as expected, and all remaining elements will be ignored. - // hence range[Target - 1] will have the last range as we need. + // Hence range[Target - 1] will have the last range as we need. _ = line.TrySplit(" ", range, out int numRanges, StringComparison.OrdinalIgnoreCase, StringSplitOptions.RemoveEmptyEntries); #endif if (numRanges < Target) @@ -86,10 +86,10 @@ private static void UpdateTcpStateInfo(ReadOnlySpan buffer, TcpStateInfo t ReadOnlySpan tcpConnectionState = line.Slice(range[Target - 1].Index, range[Target - 1].Count); #endif - // at this point, tcpConnectionState contains one of TCP connection states in hexadecimal format, e.g., "01", + // At this point, tcpConnectionState contains one of TCP connection states in hexadecimal format, e.g., "01", // which we now need to convert to the LinuxTcpState enum. - // note: until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 - // we have to allocate & throw away memory using .ToString() + // Note: until this API proposal is implemented https://github.com/dotnet/runtime/issues/61397 + // we have to allocate and throw away memory using .ToString(). var state = (LinuxTcpState)Convert.ToInt32(tcpConnectionState.ToString(), Base16); switch (state) { diff --git a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs index 60332880cd7..be93f3b9fb1 100644 --- a/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs +++ b/src/Libraries/Microsoft.Extensions.Diagnostics.ResourceMonitoring/Windows/Network/WindowsNetworkMetrics.cs @@ -17,7 +17,7 @@ public WindowsNetworkMetrics(IMeterFactory meterFactory, ITcpStateInfoProvider t #pragma warning disable CA2000 // Dispose objects before losing scope // We don't dispose the meter because IMeterFactory handles that - // An issue on analyzer side: https://github.com/dotnet/roslyn-analyzers/issues/6912 + // Is's a false-positive, see: https://github.com/dotnet/roslyn-analyzers/issues/6912. // Related documentation: https://github.com/dotnet/docs/pull/37170 var meter = meterFactory.Create(nameof(ResourceMonitoring)); #pragma warning restore CA2000 // Dispose objects before losing scope diff --git a/src/Shared/StringSplit/README.md b/src/Shared/StringSplit/README.md index 9c9f1110e7e..0ba6e2437eb 100644 --- a/src/Shared/StringSplit/README.md +++ b/src/Shared/StringSplit/README.md @@ -1,6 +1,6 @@ # Numeric Extensions -`StringSplit` API to get allocation free string splitting for .NET runtime before .NET 8 +`StringSplit` API to get allocation-free string splitting for .NET runtime before .NET 8 To use this in your project, add the following to your `.csproj` file: diff --git a/src/Shared/StringSplit/StringRange.cs b/src/Shared/StringSplit/StringRange.cs index e120dc3a869..79d92a35c7e 100644 --- a/src/Shared/StringSplit/StringRange.cs +++ b/src/Shared/StringSplit/StringRange.cs @@ -68,7 +68,7 @@ public int CompareTo(object? obj) return CompareTo(ss); } - if (obj != null) + if (obj is not null) { Throw.ArgumentException(nameof(obj), $"Provided value must be of type {typeof(StringRange)}, but was of type {obj.GetType()}."); } diff --git a/src/Shared/StringSplit/StringSplitExtensions.cs b/src/Shared/StringSplit/StringSplitExtensions.cs index cbc8aedc805..c6ef8e6794a 100644 --- a/src/Shared/StringSplit/StringSplitExtensions.cs +++ b/src/Shared/StringSplit/StringSplitExtensions.cs @@ -21,7 +21,7 @@ internal static class StringSplitExtensions /// A span to receive the individual string segments. /// The number of string segments copied to the output. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this ReadOnlySpan input, char separator, @@ -79,7 +79,7 @@ public static bool TrySplit( /// A span to receive the individual string segments. /// The number of string segments copied to the output. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this ReadOnlySpan input, ReadOnlySpan separators, @@ -138,7 +138,7 @@ public static bool TrySplit( /// The number of string segments copied to the output. /// The kind of string comparison to apply to the separator strings. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this ReadOnlySpan input, string[] separators, @@ -211,7 +211,7 @@ public static bool TrySplit( /// The number of string segments copied to the output. /// The kind of string comparison to apply to the separator strings. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this ReadOnlySpan input, string separator, @@ -280,7 +280,7 @@ public static bool TrySplit( /// A span to receive the individual string segments. /// The number of string segments copied to the output. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . /// This uses whitespace as a separator of individual substrings. public static bool TrySplit( this ReadOnlySpan input, @@ -347,7 +347,7 @@ public static bool TrySplit( /// A span to receive the individual string segments. /// The number of string segments copied to the output. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this string input, char separator, @@ -364,7 +364,7 @@ public static bool TrySplit( /// A span to receive the individual string segments. /// The number of string segments copied to the output. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this string input, ReadOnlySpan separators, @@ -382,7 +382,7 @@ public static bool TrySplit( /// The number of string segments copied to the output. /// The kind of string comparison to apply to the separator strings. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this string input, string[] separators, @@ -401,7 +401,7 @@ public static bool TrySplit( /// The number of string segments copied to the output. /// The kind of string comparison to apply to the separator strings. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . public static bool TrySplit( this string input, string separator, @@ -418,7 +418,7 @@ public static bool TrySplit( /// A span to receive the individual string segments. /// The number of string segments copied to the output. /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array, otherwise . + /// if there was enough space in the output array; otherwise, . /// This uses whitespace as a separator of individual substrings. public static bool TrySplit( this string input, From 8f0215c499a22d6f176b237858b5a8247d1af168 Mon Sep 17 00:00:00 2001 From: evgenyfedorov2 <25526458+evgenyfedorov2@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:15:50 +0200 Subject: [PATCH 13/13] Remove extra stringSplit extensions --- .../StringSplit/StringSplitExtensions.cs | 806 +----------------- .../StringSplit/SplitExtensionsTests.cs | 349 +------- 2 files changed, 9 insertions(+), 1146 deletions(-) diff --git a/src/Shared/StringSplit/StringSplitExtensions.cs b/src/Shared/StringSplit/StringSplitExtensions.cs index c6ef8e6794a..cee4579d1e1 100644 --- a/src/Shared/StringSplit/StringSplitExtensions.cs +++ b/src/Shared/StringSplit/StringSplitExtensions.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NET8_0_OR_GREATER +#if NET6_0 using System; using Microsoft.Shared.Diagnostics; @@ -13,195 +13,6 @@ namespace Microsoft.Shared.StringSplit; #endif internal static class StringSplitExtensions { - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// A character that delimits the substrings in this instance. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this ReadOnlySpan input, - char separator, - Span result, - out int numSegments, - StringSplitOptions options = StringSplitOptions.None) - { - const int SeparatorLen = 1; - - CheckStringSplitOptions(options); - - numSegments = 0; - - int start = 0; - while (true) - { - int index = input.Slice(start).IndexOf(separator); - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - if (numSegments >= result.Length) - { - return false; - } - - result[numSegments++] = new StringRange(rangeStart, sp.Length); - } - - if (index < 0) - { - return true; - } - - start += index + SeparatorLen; - } - } - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// The characters that delimit the substrings in this instance. This is not treated as a string, this is used as an array of individual characters. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this ReadOnlySpan input, - ReadOnlySpan separators, - Span result, - out int numSegments, - StringSplitOptions options = StringSplitOptions.None) - { - const int SeparatorLen = 1; - - CheckStringSplitOptions(options); - - numSegments = 0; - - int start = 0; - while (true) - { - int index = input.Slice(start).IndexOfAny(separators); - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - if (numSegments >= result.Length) - { - return false; - } - - result[numSegments++] = new StringRange(rangeStart, sp.Length); - } - - if (index < 0) - { - return true; - } - - start += index + SeparatorLen; - } - } - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// The strings that delimit the substrings in this instance. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this ReadOnlySpan input, - string[] separators, - Span result, - out int numSegments, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) - { - _ = Throw.IfNull(separators); - CheckStringSplitOptions(options); - - numSegments = 0; - - int start = 0; - while (true) - { - int index = -1; - int separatorLen = 0; - foreach (var sep in separators) - { - int found = input.Slice(start).IndexOf(sep.AsSpan(), comparison); - if (found >= 0) - { - if (found < index || index < 0) - { - separatorLen = sep.Length; - index = found; - } - } - } - - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - if (numSegments >= result.Length) - { - return false; - } - - result[numSegments++] = new StringRange(rangeStart, sp.Length); - } - - if (index < 0) - { - return true; - } - - start += index + separatorLen; - } - } - /// /// Splits a string into a number of string segments. /// @@ -244,7 +55,6 @@ public static bool TrySplit( var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); var rangeStart = start; -#if NET5_0_OR_GREATER if ((options & StringSplitOptions.TrimEntries) != 0) { var len = sp.Length; @@ -252,7 +62,6 @@ public static bool TrySplit( rangeStart = start + len - sp.Length; sp = sp.TrimEnd(); } -#endif if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) { @@ -273,619 +82,16 @@ public static bool TrySplit( } } - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - /// This uses whitespace as a separator of individual substrings. - public static bool TrySplit( - this ReadOnlySpan input, - Span result, - out int numSegments, - StringSplitOptions options = StringSplitOptions.None) + private static void CheckStringSplitOptions(StringSplitOptions options) { - const int SeparatorLen = 1; - - CheckStringSplitOptions(options); - - numSegments = 0; + const StringSplitOptions AllValidFlags = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; - int start = 0; - while (true) + if ((options & ~AllValidFlags) != 0) { - int index = -1; - for (int i = start; i < input.Length; i++) - { - if (char.IsWhiteSpace(input[i])) - { - index = i - start; - break; - } - } - - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - if (numSegments >= result.Length) - { - return false; - } - - result[numSegments++] = new StringRange(rangeStart, sp.Length); - } - - if (index < 0) - { - return true; - } - - start += index + SeparatorLen; + // at least one invalid flag was set + Throw.ArgumentException(nameof(options), "Invalid split options specified"); } } - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// A character that delimits the substrings in this instance. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this string input, - char separator, - Span result, - out int numSegments, - StringSplitOptions options = StringSplitOptions.None) - => TrySplit(input.AsSpan(), separator, result, out numSegments, options); - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// The characters that delimit the substrings in this instance. This is not treated as a string, this is used as an array of individual characters. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this string input, - ReadOnlySpan separators, - Span result, - out int numSegments, - StringSplitOptions options = StringSplitOptions.None) - => TrySplit(input.AsSpan(), separators, result, out numSegments, options); - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// The strings that delimit the substrings in this instance. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this string input, - string[] separators, - Span result, - out int numSegments, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) - => TrySplit(input.AsSpan(), separators, result, out numSegments, comparison, options); - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// The string that delimits the substrings in this instance. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - public static bool TrySplit( - this string input, - string separator, - Span result, - out int numSegments, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) - => TrySplit(input.AsSpan(), separator, result, out numSegments, comparison, options); - - /// - /// Splits a string into a number of string segments. - /// - /// The string to split. - /// A span to receive the individual string segments. - /// The number of string segments copied to the output. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// if there was enough space in the output array; otherwise, . - /// This uses whitespace as a separator of individual substrings. - public static bool TrySplit( - this string input, - Span result, - out int numSegments, - StringSplitOptions options = StringSplitOptions.None) => TrySplit(input.AsSpan(), result, out numSegments, options); - - private static void CheckStringSplitOptions(StringSplitOptions options) - { -#if NET5_0_OR_GREATER - const StringSplitOptions AllValidFlags = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; -#else - const StringSplitOptions AllValidFlags = StringSplitOptions.RemoveEmptyEntries; -#endif - - if ((options & ~AllValidFlags) != 0) - { - // at least one invalid flag was set - Throw.ArgumentException(nameof(options), "Invalid split options specified"); - } - } - - /// - /// The delegate that gets invoked when visiting the splits of a string. - /// - /// Type of the context value given to the delegate. - /// The span of characters that makes up the split. - /// The monotonically increasing split count. - /// The user-defined context object. - public delegate void SplitVisitor(ReadOnlySpan split, int segmentNum, T context); - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// A character that delimits the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this ReadOnlySpan input, - char separator, - TContext context, - SplitVisitor visitor, - StringSplitOptions options = StringSplitOptions.None) - { - const int SeparatorLen = 1; - - _ = Throw.IfNull(visitor); - CheckStringSplitOptions(options); - - int numSegments = 0; - int start = 0; - while (true) - { - int index = input.Slice(start).IndexOf(separator); - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); - } - - if (index < 0) - { - return; - } - - start += index + SeparatorLen; - } - } - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// The characters that delimit the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this ReadOnlySpan input, - ReadOnlySpan separators, - TContext context, - SplitVisitor visitor, - StringSplitOptions options = StringSplitOptions.None) - { - const int SeparatorLen = 1; - - _ = Throw.IfNull(visitor); - CheckStringSplitOptions(options); - - int numSegments = 0; - int start = 0; - while (true) - { - int index = input.Slice(start).IndexOfAny(separators); - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); - } - - if (index < 0) - { - return; - } - - start += index + SeparatorLen; - } - } - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// The strings that delimit the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this ReadOnlySpan input, - string[] separators, - TContext context, - SplitVisitor visitor, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) - { - _ = Throw.IfNull(separators); - _ = Throw.IfNull(visitor); - CheckStringSplitOptions(options); - - int numSegments = 0; - int start = 0; - while (true) - { - int index = -1; - int separatorLen = 0; - foreach (var sep in separators) - { - int found = input.Slice(start).IndexOf(sep.AsSpan(), comparison); - if (found >= 0) - { - if (found < index || index < 0) - { - separatorLen = sep.Length; - index = found; - } - } - } - - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); - } - - if (index < 0) - { - return; - } - - start += index + separatorLen; - } - } - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// The string that delimits the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static void VisitSplits( - this ReadOnlySpan input, - string separator, - TContext context, - SplitVisitor visitor, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - { - _ = Throw.IfNull(separator); - _ = Throw.IfNull(visitor); - CheckStringSplitOptions(options); - - int numSegments = 0; - int start = 0; - while (true) - { - int index = -1; - int separatorLen = 0; - - int found = input.Slice(start).IndexOf(separator.AsSpan(), comparison); - if (found >= 0) - { - if (found < index || index < 0) - { - separatorLen = separator.Length; - index = found; - } - } - - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); - } - - if (index < 0) - { - return; - } - - start += index + separatorLen; - } - } - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// This uses whitespace as a separator of individual substrings. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this ReadOnlySpan input, - TContext context, - SplitVisitor visitor, - StringSplitOptions options = StringSplitOptions.None) - { - const int SeparatorLen = 1; - - _ = Throw.IfNull(visitor); - CheckStringSplitOptions(options); - - int numSegments = 0; - int start = 0; - while (true) - { - int index = -1; - for (int i = start; i < input.Length; i++) - { - if (char.IsWhiteSpace(input[i])) - { - index = i - start; - break; - } - } - - var sp = index < 0 ? input.Slice(start) : input.Slice(start, index); - - var rangeStart = start; -#if NET5_0_OR_GREATER - if ((options & StringSplitOptions.TrimEntries) != 0) - { - var len = sp.Length; - sp = sp.TrimStart(); - rangeStart = start + len - sp.Length; - sp = sp.TrimEnd(); - } -#endif - - if (sp.Length > 0 || (options & StringSplitOptions.RemoveEmptyEntries) == 0) - { - visitor(input.Slice(rangeStart, sp.Length), numSegments++, context); - } - - if (index < 0) - { - return; - } - - start += index + SeparatorLen; - } - } - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// A character that delimits the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static void VisitSplits( - this string input, - char separator, - TContext context, - SplitVisitor visitor, - StringSplitOptions options = StringSplitOptions.None) - => VisitSplits(input.AsSpan(), separator, context, visitor, options); -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// The characters that delimit the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// -#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public static void VisitSplits( - this string input, - ReadOnlySpan separators, - TContext context, - SplitVisitor visitor, - StringSplitOptions options = StringSplitOptions.None) - => VisitSplits(input.AsSpan(), separators, context, visitor, options); -#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// The strings that delimit the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this string input, - string[] separators, - TContext context, - SplitVisitor visitor, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) - => VisitSplits(input.AsSpan(), separators, context, visitor, comparison, options); - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// The string that delimits the substrings in this instance. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// The kind of string comparison to apply to the separator strings. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this string input, - string separator, - TContext context, - SplitVisitor visitor, - StringComparison comparison = StringComparison.Ordinal, - StringSplitOptions options = StringSplitOptions.None) - => VisitSplits(input.AsSpan(), separator, context, visitor, comparison, options); - - /// - /// Invokes a delegate for individual string segments. - /// - /// The string to split. - /// An object that can be used to pass state to the visitor. - /// A delegate that gets invoked for each individual segment. - /// A bitwise combination of the enumeration values that specifies whether to trim substrings and include empty substrings. - /// The type of the visitor's context. - /// - /// This uses whitespace as a separator of individual substrings. - /// - /// The visitor delegate is invoked for each segment in the input. It is given as parameter the - /// value of the argument, the segment index, and a range for the segment. - /// - public static void VisitSplits( - this string input, - TContext context, - SplitVisitor visitor, - StringSplitOptions options = StringSplitOptions.None) - => VisitSplits(input.AsSpan(), context, visitor, options); } #endif diff --git a/test/Shared/StringSplit/SplitExtensionsTests.cs b/test/Shared/StringSplit/SplitExtensionsTests.cs index bb96612658a..fc64ad7ef34 100644 --- a/test/Shared/StringSplit/SplitExtensionsTests.cs +++ b/test/Shared/StringSplit/SplitExtensionsTests.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#if !NET8_0_OR_GREATER +#if NET6_0 using System; using System.Collections.Generic; @@ -11,146 +11,6 @@ namespace Microsoft.Shared.StringSplit.Test; public static class SplitExtensionsTests { - public static IEnumerable SingleCharData => new List - { - new object[] { string.Empty, StringSplitOptions.None }, - new object[] { "A", StringSplitOptions.None }, - new object[] { "AA", StringSplitOptions.None }, - new object[] { "/", StringSplitOptions.None }, - new object[] { "A/", StringSplitOptions.None }, - new object[] { "AA/", StringSplitOptions.None }, - new object[] { "/A", StringSplitOptions.None }, - new object[] { "/AA", StringSplitOptions.None }, - new object[] { "AA/B", StringSplitOptions.None }, - new object[] { "AA//", StringSplitOptions.None }, - new object[] { "AA//BB", StringSplitOptions.None }, - - new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, - new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "/", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "A/", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA/", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "/A", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "/AA", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA/B", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA//", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA//BB", StringSplitOptions.RemoveEmptyEntries }, - -#if NET5_0_OR_GREATER - new object[] { string.Empty, StringSplitOptions.TrimEntries }, - new object[] { " ", StringSplitOptions.TrimEntries }, - new object[] { " A", StringSplitOptions.TrimEntries }, - new object[] { "AA", StringSplitOptions.TrimEntries }, - new object[] { "A A", StringSplitOptions.TrimEntries }, - new object[] { "/", StringSplitOptions.TrimEntries }, - new object[] { " /", StringSplitOptions.TrimEntries }, - new object[] { "A/", StringSplitOptions.TrimEntries }, - new object[] { "A /", StringSplitOptions.TrimEntries }, - new object[] { "AA/", StringSplitOptions.TrimEntries }, - new object[] { "/A", StringSplitOptions.TrimEntries }, - new object[] { " / A ", StringSplitOptions.TrimEntries }, - new object[] { "/AA", StringSplitOptions.TrimEntries }, - new object[] { "AA/B", StringSplitOptions.TrimEntries }, - new object[] { "AA//", StringSplitOptions.TrimEntries }, - new object[] { "AA//BB", StringSplitOptions.TrimEntries }, - new object[] { " abcde /fghijk /lmn", StringSplitOptions.TrimEntries }, -#endif - }; - - public static IEnumerable MultiCharData => new List - { - new object[] { string.Empty, StringSplitOptions.None }, - new object[] { "A", StringSplitOptions.None }, - new object[] { "AA", StringSplitOptions.None }, - new object[] { "/", StringSplitOptions.None }, - new object[] { "A\\", StringSplitOptions.None }, - new object[] { "AA/", StringSplitOptions.None }, - new object[] { "/A", StringSplitOptions.None }, - new object[] { "\\AA", StringSplitOptions.None }, - new object[] { "AA/B", StringSplitOptions.None }, - new object[] { "AA/\\", StringSplitOptions.None }, - new object[] { "AA//BB", StringSplitOptions.None }, - - new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, - new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "/", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "A/", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA\\", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "/A", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "/AA", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA/B", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA//", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA//BB", StringSplitOptions.RemoveEmptyEntries }, - -#if NET5_0_OR_GREATER - new object[] { string.Empty, StringSplitOptions.TrimEntries }, - new object[] { " ", StringSplitOptions.TrimEntries }, - new object[] { " A", StringSplitOptions.TrimEntries }, - new object[] { "AA", StringSplitOptions.TrimEntries }, - new object[] { "A A", StringSplitOptions.TrimEntries }, - new object[] { "/", StringSplitOptions.TrimEntries }, - new object[] { " /", StringSplitOptions.TrimEntries }, - new object[] { "A/", StringSplitOptions.TrimEntries }, - new object[] { "A /", StringSplitOptions.TrimEntries }, - new object[] { "AA/", StringSplitOptions.TrimEntries }, - new object[] { "/A", StringSplitOptions.TrimEntries }, - new object[] { " / A ", StringSplitOptions.TrimEntries }, - new object[] { "/AA", StringSplitOptions.TrimEntries }, - new object[] { "AA/B", StringSplitOptions.TrimEntries }, - new object[] { "AA//", StringSplitOptions.TrimEntries }, - new object[] { "AA//BB", StringSplitOptions.TrimEntries }, - new object[] { " abcde //fghijk //lmn", StringSplitOptions.TrimEntries }, -#endif - }; - - public static IEnumerable WhitespaceData => new List - { - new object[] { string.Empty, StringSplitOptions.None }, - new object[] { "A", StringSplitOptions.None }, - new object[] { "AA", StringSplitOptions.None }, - new object[] { " ", StringSplitOptions.None }, - new object[] { "A ", StringSplitOptions.None }, - new object[] { "AA ", StringSplitOptions.None }, - new object[] { " A", StringSplitOptions.None }, - new object[] { " AA", StringSplitOptions.None }, - new object[] { "AA B", StringSplitOptions.None }, - new object[] { "AA ", StringSplitOptions.None }, - new object[] { "AA BB", StringSplitOptions.None }, - - new object[] { string.Empty, StringSplitOptions.RemoveEmptyEntries }, - new object[] { "A", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA", StringSplitOptions.RemoveEmptyEntries }, - new object[] { " ", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "A ", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA ", StringSplitOptions.RemoveEmptyEntries }, - new object[] { " A", StringSplitOptions.RemoveEmptyEntries }, - new object[] { " AA", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA B", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA ", StringSplitOptions.RemoveEmptyEntries }, - new object[] { "AA BB", StringSplitOptions.RemoveEmptyEntries }, - -#if NET5_0_OR_GREATER - new object[] { string.Empty, StringSplitOptions.TrimEntries }, - new object[] { " ", StringSplitOptions.TrimEntries }, - new object[] { " A", StringSplitOptions.TrimEntries }, - new object[] { "AA", StringSplitOptions.TrimEntries }, - new object[] { "A A", StringSplitOptions.TrimEntries }, - new object[] { " ", StringSplitOptions.TrimEntries }, - new object[] { " ", StringSplitOptions.TrimEntries }, - new object[] { "A ", StringSplitOptions.TrimEntries }, - new object[] { "A ", StringSplitOptions.TrimEntries }, - new object[] { "AA ", StringSplitOptions.TrimEntries }, - new object[] { " A", StringSplitOptions.TrimEntries }, - new object[] { " A ", StringSplitOptions.TrimEntries }, - new object[] { " AA", StringSplitOptions.TrimEntries }, - new object[] { "AA B", StringSplitOptions.TrimEntries }, - new object[] { "AA ", StringSplitOptions.TrimEntries }, - new object[] { "AA BB", StringSplitOptions.TrimEntries }, -#endif - }; - public static IEnumerable StringData => new List { new object[] { string.Empty, StringSplitOptions.None }, @@ -177,7 +37,6 @@ public static class SplitExtensionsTests new object[] { "AAXX", StringSplitOptions.RemoveEmptyEntries }, new object[] { "AAYYBB", StringSplitOptions.RemoveEmptyEntries }, -#if NET5_0_OR_GREATER new object[] { string.Empty, StringSplitOptions.TrimEntries }, new object[] { "XX", StringSplitOptions.TrimEntries }, new object[] { "YYA", StringSplitOptions.TrimEntries }, @@ -195,82 +54,8 @@ public static class SplitExtensionsTests new object[] { "AAXXYY", StringSplitOptions.TrimEntries }, new object[] { "AAYYXXBB", StringSplitOptions.TrimEntries }, new object[] { " abcde XXfghijk YYlmn", StringSplitOptions.TrimEntries }, -#endif }; - [Theory] - [MemberData(nameof(SingleCharData))] - public static void SingleChar(string input, StringSplitOptions options) - { - var expected = input.Split(new[] { '/' }, options); - - var actual = new StringRange[20]; - Assert.True(input.TrySplit('/', actual, out int numActuals, options)); - - Assert.Equal(expected.Length, numActuals); - Assert.Equal(expected.Length == 0, input.TrySplit('/', Array.Empty(), out _, options)); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); - } - } - - [Theory] - [MemberData(nameof(MultiCharData))] - public static void MultiChar(string input, StringSplitOptions options) - { - var expected = input.Split(new[] { '/', '\\' }, options); - - var actual = new StringRange[20]; - Assert.True(input.TrySplit(new[] { '/', '\\' }, actual, out int numActuals, options)); - - Assert.Equal(expected.Length, numActuals); - Assert.Equal(expected.Length == 0, input.TrySplit(new[] { '/', '\\' }, Array.Empty(), out _, options)); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); - } - } - - [Theory] - [MemberData(nameof(WhitespaceData))] - public static void Whitespace(string input, StringSplitOptions options) - { - var expected = input.Split((string[]?)null, options); - - var actual = new StringRange[20]; - Assert.True(input.TrySplit(actual, out int numActuals, options)); - - Assert.Equal(expected.Length, numActuals); - Assert.Equal(expected.Length == 0, input.TrySplit(Array.Empty(), out _, options)); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); - } - } - - [Theory] - [MemberData(nameof(StringData))] - public static void StringArray(string input, StringSplitOptions options) - { - var expected = input.Split(new[] { "XX", "YY" }, options); - - var actual = new StringRange[20]; - Assert.True(input.TrySplit(new[] { "XX", "YY" }, actual, out int numActuals, StringComparison.Ordinal, options)); - - Assert.Equal(expected.Length, numActuals); - Assert.Equal(expected.Length == 0, input.TrySplit(new[] { "XX", "YY" }, Array.Empty(), out _, StringComparison.Ordinal, options)); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], input.Substring(actual[i].Index, actual[i].Count)); - } - } - -#if NETCOREAPP3_1_OR_GREATER [Theory] [MemberData(nameof(StringData))] public static void Strings(string input, StringSplitOptions options) @@ -282,10 +67,10 @@ public static void Strings(string input, StringSplitOptions options) var expected = input.Split(separator, options); var actual = new StringRange[20]; - Assert.True(input.TrySplit(separator, actual, out int numActuals, StringComparison.Ordinal, options)); + Assert.True(input.AsSpan().TrySplit(separator, actual, out int numActuals, StringComparison.Ordinal, options)); Assert.Equal(expected.Length, numActuals); - Assert.Equal(expected.Length == 0, input.TrySplit(separator, Array.Empty(), out _, StringComparison.Ordinal, options)); + Assert.Equal(expected.Length == 0, input.AsSpan().TrySplit(separator, Array.Empty(), out _, StringComparison.Ordinal, options)); for (int j = 0; j < expected.Length; j++) { @@ -293,134 +78,6 @@ public static void Strings(string input, StringSplitOptions options) } } } -#endif - - [Theory] - [MemberData(nameof(SingleCharData))] - public static void VisitSingleChar(string input, StringSplitOptions options) - { - var expected = input.Split(new[] { '/' }, options); - - var actual = new string[20]; - int numActuals = 0; - input.VisitSplits('/', actual, (s, c, a) => - { - a[c] = s.ToString(); - numActuals++; - }, options); - - Assert.Equal(expected.Length, numActuals); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - - [Theory] - [MemberData(nameof(MultiCharData))] - public static void VisitMultiChar(string input, StringSplitOptions options) - { - var expected = input.Split(new[] { '/', '\\' }, options); - - var actual = new string[20]; - int numActuals = 0; - input.VisitSplits(new[] { '/', '\\' }, actual, (s, c, a) => - { - a[c] = s.ToString(); - numActuals++; - }, options); - - Assert.Equal(expected.Length, numActuals); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - - [Theory] - [MemberData(nameof(WhitespaceData))] - public static void VisitWhitespace(string input, StringSplitOptions options) - { - var expected = input.Split((string[]?)null, options); - - var actual = new string[20]; - int numActuals = 0; - input.VisitSplits(actual, (s, c, a) => - { - a[c] = s.ToString(); - numActuals++; - }, options); - - Assert.Equal(expected.Length, numActuals); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - - [Theory] - [MemberData(nameof(StringData))] - public static void VisitStringArray(string input, StringSplitOptions options) - { - var expected = input.Split(new[] { "XX", "YY" }, options); - - var actual = new string[20]; - int numActuals = 0; - input.VisitSplits(new[] { "XX", "YY" }, actual, (s, c, a) => - { - a[c] = s.ToString(); - numActuals++; - }, StringComparison.Ordinal, options); - - Assert.Equal(expected.Length, numActuals); - - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } - -#if NETCOREAPP3_1_OR_GREATER - [Theory] - [MemberData(nameof(StringData))] - public static void VisitStrings(string input, StringSplitOptions options) - { - for (int i = 0; i < 2; i++) - { - var separator = i == 0 ? "XX" : "YY"; - - var expected = input.Split(separator, options); - - var actual = new string[20]; - int numActuals = 0; - input.VisitSplits(separator, actual, (s, c, a) => - { - a[c] = s.ToString(); - numActuals++; - }, StringComparison.Ordinal, options); - - Assert.Equal(expected.Length, numActuals); - - for (int j = 0; j < expected.Length; j++) - { - Assert.Equal(expected[j], actual[j]); - } - } - } -#endif - - [Fact] - public static void CheckOpts() - { - var ss = new StringRange[1]; - Assert.Throws(() => "ABC".TrySplit(new[] { '/', '\\' }, ss, out _, (StringSplitOptions)(-1))); - Assert.Throws(() => "ABC".TrySplit(new[] { "XX", "YY" }, ss, out _, StringComparison.Ordinal, (StringSplitOptions)(-1))); - Assert.Throws(() => "ABC".TrySplit('/', ss, out _, (StringSplitOptions)(-1))); - Assert.Throws(() => "ABC".TrySplit(ss, out _, (StringSplitOptions)(-1))); - } } #endif