From 46caaac9debdaad496c07af9d3806e67a447066c Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 18 Jan 2023 22:43:14 -0500 Subject: Heap diag and plugin log file names --- lib/Net.Http/src/IHeaderCollection.cs | 4 +- lib/Net.Messaging.FBM/src/Client/Helpers.cs | 5 +- .../src/Accounts/PasswordHashing.cs | 3 + lib/Plugins.PluginBase/src/PluginBase.cs | 10 +- lib/Utils/README.md | 3 + lib/Utils/src/Extensions/TimerExtensions.cs | 4 +- lib/Utils/src/Extensions/TimerResetHandle.cs | 66 -------- lib/Utils/src/Extensions/TimerResumeHandle.cs | 66 ++++++++ .../Memory/Diagnostics/HeapDiagnosticException.cs | 43 +++++ lib/Utils/src/Memory/Diagnostics/HeapStatistics.cs | 76 +++++++++ .../Diagnostics/IllegalHeapOperationException.cs | 43 +++++ .../src/Memory/Diagnostics/TrackedHeapWrapper.cs | 178 +++++++++++++++++++++ lib/Utils/src/Memory/MemoryUtil.cs | 125 ++++++++++----- lib/Utils/src/Memory/RpMallocPrivateHeap.cs | 2 +- lib/Utils/src/Memory/Win32PrivateHeap.cs | 4 +- lib/Utils/tests/.runsettings | 8 + lib/Utils/tests/Memory/MemoryHandleTest.cs | 2 +- lib/Utils/tests/Memory/MemoryUtilTests.cs | 77 ++++++++- lib/Utils/tests/Memory/VnTableTests.cs | 4 +- 19 files changed, 605 insertions(+), 118 deletions(-) delete mode 100644 lib/Utils/src/Extensions/TimerResetHandle.cs create mode 100644 lib/Utils/src/Extensions/TimerResumeHandle.cs create mode 100644 lib/Utils/src/Memory/Diagnostics/HeapDiagnosticException.cs create mode 100644 lib/Utils/src/Memory/Diagnostics/HeapStatistics.cs create mode 100644 lib/Utils/src/Memory/Diagnostics/IllegalHeapOperationException.cs create mode 100644 lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs create mode 100644 lib/Utils/tests/.runsettings (limited to 'lib') diff --git a/lib/Net.Http/src/IHeaderCollection.cs b/lib/Net.Http/src/IHeaderCollection.cs index f7f147a..b57f5bf 100644 --- a/lib/Net.Http/src/IHeaderCollection.cs +++ b/lib/Net.Http/src/IHeaderCollection.cs @@ -70,13 +70,13 @@ namespace VNLib.Net.Http bool HeaderSet(HttpRequestHeader header); /// - /// Overwrites (sets) the given response header to the exact value specified + /// Appends the header value to the current header value /// /// The enumrated header id /// The value to specify void Append(HttpResponseHeader header, string? value); /// - /// Overwrites (sets) the given response header to the exact value specified + /// Appends the header value to the current header value /// /// The header name /// The value to specify diff --git a/lib/Net.Messaging.FBM/src/Client/Helpers.cs b/lib/Net.Messaging.FBM/src/Client/Helpers.cs index 8f895fa..054a30b 100644 --- a/lib/Net.Messaging.FBM/src/Client/Helpers.cs +++ b/lib/Net.Messaging.FBM/src/Client/Helpers.cs @@ -78,6 +78,7 @@ namespace VNLib.Net.Messaging.FBM /// Alloctes a random integer to use as a message id /// public static int RandomMessageId => RandomNumberGenerator.GetInt32(1, int.MaxValue); + /// /// Gets the remaining data after the current position of the stream. /// @@ -197,7 +198,7 @@ namespace VNLib.Net.Messaging.FBM /// The of the header /// The value of the header /// Encoding to use when writing character message - /// + /// public static void WriteHeader(ref this ForwardOnlyWriter buffer, byte header, ReadOnlySpan value, Encoding encoding) { //get char count @@ -205,7 +206,7 @@ namespace VNLib.Net.Messaging.FBM //make sure there is enough room in the buffer if (buffer.RemainingSize < byteCount) { - throw new OutOfMemoryException(); + throw new ArgumentOutOfRangeException(nameof(value),"The internal buffer is too small to write header"); } //Write header command enum value buffer.Append(header); diff --git a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs index 9dc3ea1..553b41c 100644 --- a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs +++ b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs @@ -86,6 +86,7 @@ namespace VNLib.Plugins.Essentials.Accounts //Casting PrivateStrings to spans will reference the base string directly return Verify((ReadOnlySpan)passHash, (ReadOnlySpan)password); } + /// /// Verifies a password against its previously encoded hash. /// @@ -116,6 +117,7 @@ namespace VNLib.Plugins.Essentials.Accounts MemoryUtil.InitializeBlock(secretBuffer.Span); } } + /// /// Verifies a password against its hash. Partially exposes the Argon2 api. /// @@ -203,6 +205,7 @@ namespace VNLib.Plugins.Essentials.Accounts MemoryUtil.InitializeBlock(buffer.Span); } } + /// /// Partially exposes the Argon2 api. Hashes the specified password, with the initialized pepper. /// Writes the raw hash output to the specified buffer diff --git a/lib/Plugins.PluginBase/src/PluginBase.cs b/lib/Plugins.PluginBase/src/PluginBase.cs index e47ba67..105deab 100644 --- a/lib/Plugins.PluginBase/src/PluginBase.cs +++ b/lib/Plugins.PluginBase/src/PluginBase.cs @@ -197,11 +197,19 @@ namespace VNLib.Plugins { interval = Enum.Parse(intervalEl.GetString()!, true); } + + if(filePath != null) + { + //Get the file name to replace with the plugin name + string appLogName = Path.GetFileNameWithoutExtension(filePath); + + //Replace the file name + filePath = filePath.Replace(appLogName, PluginName, StringComparison.Ordinal); + } } //Default if not set filePath ??= Path.Combine(Environment.CurrentDirectory, $"{PluginName}.txt"); - template ??= LogTemplate; //Configure the log file writer logConfig.WriteTo.File(filePath, diff --git a/lib/Utils/README.md b/lib/Utils/README.md index 683cc3c..a3be003 100644 --- a/lib/Utils/README.md +++ b/lib/Utils/README.md @@ -26,6 +26,9 @@ Valid allocator value for the `VNLIB_SHARED_HEAP_TYPE` environment variable: - "rpmalloc" - to load the RPMalloc native library if compiled for your platform - none - the default value, will attempt to load the win32 private heap Kernel32 library, otherwise, the native ProcessHeap() cross platform allocator +#### Heap Diagnostics +The Memory.Diagnostics namespace was added to provide a wrapper for tracking IUnmanagedHeap memory allocations. Diagnostics can be enabled for the SharedHeap by setting the `VNLIB_SHARED_HEAP_DIAGNOSTICS` environment variable to "1". When enabled, calling MemoryUtil.GetSharedHeapStats() will return the heap's current statistics, otherwise an empty struct is returned. The SharedHeap diagnostics are disabled by default. + ## Usage A usage breakdown would be far to lengthy for this library, and instead I intend to keep valid and comprehensive documentation in Visual Studio XML documentation files included in this project's src directory. diff --git a/lib/Utils/src/Extensions/TimerExtensions.cs b/lib/Utils/src/Extensions/TimerExtensions.cs index 37929bf..328f4da 100644 --- a/lib/Utils/src/Extensions/TimerExtensions.cs +++ b/lib/Utils/src/Extensions/TimerExtensions.cs @@ -47,9 +47,9 @@ namespace VNLib.Utils.Extensions /// /// representing the amount of time the timer should wait before invoking the callback function /// A new if the timer was stopped successfully that will resume the timer when closed, null otherwise - public static TimerResetHandle? Stop(this Timer timer, TimeSpan resumeTime) + public static TimerResumeHandle? Stop(this Timer timer, TimeSpan resumeTime) { - return timer.Change(Timeout.Infinite, Timeout.Infinite) ? new TimerResetHandle(timer, resumeTime) : null; + return timer.Change(Timeout.Infinite, Timeout.Infinite) ? new TimerResumeHandle(timer, resumeTime) : null; } /// diff --git a/lib/Utils/src/Extensions/TimerResetHandle.cs b/lib/Utils/src/Extensions/TimerResetHandle.cs deleted file mode 100644 index 57a71e8..0000000 --- a/lib/Utils/src/Extensions/TimerResetHandle.cs +++ /dev/null @@ -1,66 +0,0 @@ -/* -* Copyright (c) 2022 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: TimerExtensions.cs -* -* TimerExtensions.cs is part of VNLib.Utils which is part of the larger -* VNLib collection of libraries and utilities. -* -* VNLib.Utils is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published -* by the Free Software Foundation, either version 2 of the License, -* or (at your option) any later version. -* -* VNLib.Utils is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. -*/ - -using System; -using System.Threading; - -namespace VNLib.Utils.Extensions -{ - /// - /// A handle that represents a paused timer that may be resumed when the handle is disposed - /// or the Resume() method is called - /// - public readonly struct TimerResetHandle: IEquatable, IDisposable - { - private readonly Timer _timer; - private readonly TimeSpan _resumeTime; - - internal TimerResetHandle(Timer timer, TimeSpan resumeTime) - { - _timer = timer; - _resumeTime = resumeTime; - } - - /// - /// Resumes the timer to the configured time from the call to Timer.Stop() - /// - public void Resume() => _timer.Change(_resumeTime, Timeout.InfiniteTimeSpan); - /// - /// Releases any resources held by the Handle, and resumes the timer to - /// the configured time from the call to Timer.Stop() - /// - public void Dispose() => Resume(); - - /// - public bool Equals(TimerResetHandle other) => _timer.Equals(other) && _resumeTime == other._resumeTime; - /// - public override bool Equals(object? obj) => obj is TimerResetHandle trh && Equals(trh); - /// - public override int GetHashCode() => _timer.GetHashCode() + _resumeTime.GetHashCode(); - /// - public static bool operator ==(TimerResetHandle left, TimerResetHandle right) => left.Equals(right); - /// - public static bool operator !=(TimerResetHandle left, TimerResetHandle right) => !(left == right); - } -} \ No newline at end of file diff --git a/lib/Utils/src/Extensions/TimerResumeHandle.cs b/lib/Utils/src/Extensions/TimerResumeHandle.cs new file mode 100644 index 0000000..f386974 --- /dev/null +++ b/lib/Utils/src/Extensions/TimerResumeHandle.cs @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: TimerResetHandle.cs +* +* TimerResetHandle.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.Threading; + +namespace VNLib.Utils.Extensions +{ + /// + /// A handle that represents a paused timer that may be resumed when the handle is disposed + /// or the Resume() method is called + /// + public readonly struct TimerResumeHandle: IEquatable, IDisposable + { + private readonly Timer _timer; + private readonly TimeSpan _resumeTime; + + internal TimerResumeHandle(Timer timer, TimeSpan resumeTime) + { + _timer = timer; + _resumeTime = resumeTime; + } + + /// + /// Resumes the timer to the configured time from the call to Timer.Stop() + /// + public void Resume() => _timer.Change(_resumeTime, Timeout.InfiniteTimeSpan); + /// + /// Releases any resources held by the Handle, and resumes the timer to + /// the configured time from the call to Timer.Stop() + /// + public void Dispose() => Resume(); + + /// + public bool Equals(TimerResumeHandle other) => _timer.Equals(other) && _resumeTime == other._resumeTime; + /// + public override bool Equals(object? obj) => obj is TimerResumeHandle trh && Equals(trh); + /// + public override int GetHashCode() => _timer.GetHashCode() + _resumeTime.GetHashCode(); + /// + public static bool operator ==(TimerResumeHandle left, TimerResumeHandle right) => left.Equals(right); + /// + public static bool operator !=(TimerResumeHandle left, TimerResumeHandle right) => !(left == right); + } +} \ No newline at end of file diff --git a/lib/Utils/src/Memory/Diagnostics/HeapDiagnosticException.cs b/lib/Utils/src/Memory/Diagnostics/HeapDiagnosticException.cs new file mode 100644 index 0000000..af76f6e --- /dev/null +++ b/lib/Utils/src/Memory/Diagnostics/HeapDiagnosticException.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: HeapDiagnosticException.cs +* +* HeapDiagnosticException.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Utils.Memory.Diagnostics +{ + /// + /// A base class for heap memory diagnostics exceptions + /// + public class HeapDiagnosticException : NativeMemoryException + { + public HeapDiagnosticException(string message) : base(message) + {} + + public HeapDiagnosticException() + {} + + public HeapDiagnosticException(string message, Exception innerException) : base(message, innerException) + {} + } +} diff --git a/lib/Utils/src/Memory/Diagnostics/HeapStatistics.cs b/lib/Utils/src/Memory/Diagnostics/HeapStatistics.cs new file mode 100644 index 0000000..ec9fe0c --- /dev/null +++ b/lib/Utils/src/Memory/Diagnostics/HeapStatistics.cs @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: HeapStatistics.cs +* +* HeapStatistics.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Utils.Memory.Diagnostics +{ + /// + /// A structures that represents the current/last captures + /// statistics of the monitored heap + /// + public readonly struct HeapStatistics : IEquatable + { + /// + /// The current size (in bytes) of the heap + /// + public readonly ulong AllocatedBytes { get; init; } + /// + /// The largest block size seen + /// + public readonly ulong MaxBlockSize { get; init; } + /// + /// The largest size of the heap, in bytes. + /// + public readonly ulong MaxHeapSize { get; init; } + /// + /// The smallest block size seen allocated + /// + public readonly ulong MinBlockSize { get; init; } + /// + /// The number of allocated handles/blocks + /// + public readonly ulong AllocatedBlocks { get; init; } + + /// + public override bool Equals(object? obj) => obj is HeapStatistics s && Equals(s); + + /// + public override int GetHashCode() => (int)((AllocatedBytes ^ MaxBlockSize) & int.MaxValue); + + /// + public static bool operator ==(HeapStatistics left, HeapStatistics right) => left.Equals(right); + /// + public static bool operator !=(HeapStatistics left, HeapStatistics right) => !(left == right); + + /// + public bool Equals(HeapStatistics other) + { + return MinBlockSize == other.MinBlockSize + && MaxBlockSize == other.MaxBlockSize + && MaxHeapSize == other.MaxHeapSize + && AllocatedBytes == other.AllocatedBytes; + } + } +} diff --git a/lib/Utils/src/Memory/Diagnostics/IllegalHeapOperationException.cs b/lib/Utils/src/Memory/Diagnostics/IllegalHeapOperationException.cs new file mode 100644 index 0000000..6b608f2 --- /dev/null +++ b/lib/Utils/src/Memory/Diagnostics/IllegalHeapOperationException.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: IllegalHeapOperationException.cs +* +* IllegalHeapOperationException.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Utils.Memory.Diagnostics +{ + /// + /// An exception that represents an illegal memory operation during heap monitoring + /// + public class IllegalHeapOperationException : HeapDiagnosticException + { + public IllegalHeapOperationException(string message) : base(message) + {} + + public IllegalHeapOperationException(string message, Exception innerException) : base(message, innerException) + {} + + public IllegalHeapOperationException() + {} + } +} diff --git a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs new file mode 100644 index 0000000..2069d08 --- /dev/null +++ b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs @@ -0,0 +1,178 @@ +/* +* Copyright (c) 2022 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: TrackedHeapWrapper.cs +* +* TrackedHeapWrapper.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; +using System.Collections; +using System.Collections.Concurrent; + + +namespace VNLib.Utils.Memory.Diagnostics +{ + /// + /// A wrapper for that tracks + /// statistics for memory allocations. + /// + public class TrackedHeapWrapper : VnDisposeable, IUnmangedHeap + { + private readonly IUnmangedHeap _heap; + private readonly object _statsLock; + private readonly ConcurrentDictionary _table; + + /// + /// Gets the underlying heap + /// + protected IUnmangedHeap Heap => _heap; + + /// + /// Gets the internal lock held when updating statistics + /// during allocations or frees + /// + protected object StatsLock => _statsLock; + + /* Stats block */ + private ulong _alloctedBytes; + private ulong _maxBlockSize; + private ulong _maxHeapSize; + private ulong _minBlockSize; + + + /// + /// Creates a new diagnostics wrapper for the heap + /// + /// The heap to gather statistics on + public TrackedHeapWrapper(IUnmangedHeap heap) + { + _statsLock = new(); + _table = new(); + _heap = heap; + //Default min block size to 0 + _minBlockSize = ulong.MaxValue; + } + + /// + /// Captures the current state of the heap. + /// + /// A new captured from the current heap + public HeapStatistics GetCurrentStats() + { + //Aquire stats lock + lock (_statsLock) + { + return new() + { + AllocatedBytes = _alloctedBytes, + MaxBlockSize = _maxBlockSize, + MaxHeapSize = _maxHeapSize, + MinBlockSize = _minBlockSize, + //The number of elements in the table is the number of tacked blocks + AllocatedBlocks = (ulong)_table.Count + }; + } + } + + /// + public IntPtr Alloc(nuint elements, nuint size, bool zero) + { + //Calc the number of bytes allocated + ulong bytes = checked((ulong)elements * (ulong)size); + + //Alloc the block + IntPtr block = Heap.Alloc(elements, size, zero); + + //Store number of bytes allocated + _table[block] = bytes; + + lock (_statsLock) + { + UpdateStats(bytes); + } + + return block; + } + + private void UpdateStats(ulong bytes) + { + //Update stats + _alloctedBytes += bytes; + _maxBlockSize = Math.Max(_maxBlockSize, bytes); + _maxHeapSize = Math.Max(_maxHeapSize, _alloctedBytes); + _minBlockSize = Math.Min(_minBlockSize, bytes); + } + + /// + protected override void Free() + { + Heap.Dispose(); + } + + /// + public bool Free(ref IntPtr block) + { + //Remvoe the block from the table, if it has already been freed, raise exception + if(!_table.TryRemove(block, out ulong bytes)) + { + throw new IllegalHeapOperationException($"Double free detected. The block {block:x} has already been freed."); + } + + //Free the block + bool result = Heap.Free(ref block); + + //Update stats + lock (_statsLock) + { + _alloctedBytes -= bytes; + } + + return result; + } + + /// + public void Resize(ref IntPtr block, nuint elements, nuint size, bool zero) + { + //Store old block pointer + IntPtr oldBlock = block; + + //Cacl new size + ulong newSize = checked((ulong)size * (ulong)elements); + + //resize the block + Heap.Resize(ref block, elements, size, zero); + + //Remove old size + _ = _table.TryRemove(oldBlock, out ulong oldSize); + + //Update new size + _table[block] = newSize; + + lock (_statsLock) + { + //Remove old ref + _alloctedBytes -= oldSize; + + //Update stats + UpdateStats(newSize); + } + } + } +} diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 410db6b..c20d956 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Utils -* File: Memory.cs +* File: MemoryUtil.cs * -* Memory.cs is part of VNLib.Utils which is part of the larger +* MemoryUtil.cs is part of VNLib.Utils which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Utils is free software: you can redistribute it and/or modify @@ -26,10 +26,12 @@ using System; using System.Buffers; using System.Security; using System.Threading; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using VNLib.Utils.Extensions; +using VNLib.Utils.Memory.Diagnostics; namespace VNLib.Utils.Memory { @@ -40,8 +42,20 @@ namespace VNLib.Utils.Memory [ComVisible(false)] public unsafe static class MemoryUtil { + /// + /// The environment variable name used to specify the shared heap type + /// to create + /// public const string SHARED_HEAP_TYPE_ENV= "VNLIB_SHARED_HEAP_TYPE"; + /// + /// When creating a heap that accepts an initial size, this value is passed + /// to it, otherwise no initial heap size is set. + /// public const string SHARED_HEAP_INTIAL_SIZE_ENV = "VNLIB_SHARED_HEAP_SIZE"; + /// + /// The environment variable name used to enable share heap diagnostics + /// + public const string SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV = "VNLIB_SHARED_HEAP_DIAGNOSTICS"; /// /// Initial shared heap size (bytes) @@ -70,15 +84,21 @@ namespace VNLib.Utils.Memory private static readonly Lazy _sharedHeap = InitHeapInternal(); - //Avoiding statit initializer + //Avoiding static initializer private static Lazy InitHeapInternal() { - Lazy heap = new (() => InitHeapInternal(true), LazyThreadSafetyMode.PublicationOnly); + //Get env for heap diag + bool enableDiag = Environment.GetEnvironmentVariable(SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV) == "1"; + + Trace.WriteIf(enableDiag, "Shared heap diagnostics enabled"); + + Lazy heap = new (() => InitHeapInternal(true, enableDiag), LazyThreadSafetyMode.PublicationOnly); + //Cleanup the heap on process exit AppDomain.CurrentDomain.DomainUnload += DomainUnloaded; + return heap; } - private static void DomainUnloaded(object? sender, EventArgs e) { @@ -89,6 +109,23 @@ namespace VNLib.Utils.Memory } } + /// + /// Gets the shared heap statistics if stats are enabled + /// + /// + /// The of the shared heap, or an empty + /// if diagnostics are not enabled. + /// + public static HeapStatistics GetSharedHeapStats() + { + /* + * If heap is allocated and the heap type is a tracked heap, + * get the heap's stats, otherwise return an empty handle + */ + return _sharedHeap.IsValueCreated && _sharedHeap.Value is TrackedHeapWrapper h + ? h.GetCurrentStats() : new HeapStatistics(); + } + /// /// Initializes a new determined by compilation/runtime flags /// and operating system type for the current proccess. @@ -96,41 +133,45 @@ namespace VNLib.Utils.Memory /// An for the current process /// /// - public static IUnmangedHeap InitializeNewHeapForProcess() => InitHeapInternal(false); + public static IUnmangedHeap InitializeNewHeapForProcess() => InitHeapInternal(false, false); - private static IUnmangedHeap InitHeapInternal(bool isShared) + private static IUnmangedHeap InitHeapInternal(bool isShared, bool enableStats) { bool IsWindows = OperatingSystem.IsWindows(); + //Get environment varable string? heapType = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV); + //Get inital size string? sharedSize = Environment.GetEnvironmentVariable(SHARED_HEAP_INTIAL_SIZE_ENV); + //Try to parse the shared size from the env if (!nuint.TryParse(sharedSize, out nuint defaultSize)) { defaultSize = SHARED_HEAP_INIT_SIZE; } - //Gen the private heap from its type or default - switch (heapType) + + //convert to upper + heapType = heapType?.ToUpperInvariant(); + + //Create the heap + IUnmangedHeap heap = heapType switch { - case "win32": - if (!IsWindows) - { - throw new PlatformNotSupportedException("Win32 private heaps are not supported on non-windows platforms"); - } - return Win32PrivateHeap.Create(defaultSize); - case "rpmalloc": - //If the shared heap is being allocated, then return a lock free global heap - return isShared ? RpMallocPrivateHeap.GlobalHeap : new RpMallocPrivateHeap(false); - default: - return IsWindows ? Win32PrivateHeap.Create(defaultSize) : new ProcessHeap(); - } + "WIN32" => IsWindows ? Win32PrivateHeap.Create(defaultSize) : throw new PlatformNotSupportedException("Win32 private heaps are not supported on non-windows platforms"), + //If the shared heap is being allocated, then return a lock free global heap + "RPMALLOC" => isShared ? RpMallocPrivateHeap.GlobalHeap : new RpMallocPrivateHeap(false), + //Get the process heap if the heap is shared, otherwise create a new win32 private heap + _ => IsWindows && !isShared ? Win32PrivateHeap.Create(defaultSize) : new ProcessHeap(), + }; + + //If diagnosticts is enabled, wrap the heap in a stats heap + return enableStats ? new TrackedHeapWrapper(heap) : heap; } /// /// Gets a value that indicates if the Rpmalloc native library is loaded /// - public static bool IsRpMallocLoaded { get; } = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV) == "rpmalloc"; + public static bool IsRpMallocLoaded { get; } = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV)?.ToUpperInvariant() == "RPMALLOC"; #region Zero @@ -142,19 +183,23 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void UnsafeZeroMemory(ReadOnlySpan block) where T : unmanaged { - if (!block.IsEmpty) + if (block.IsEmpty) + { + return; + } + + uint byteSize = ByteCount((uint)block.Length); + + checked { - checked + fixed (void* ptr = &MemoryMarshal.GetReference(block)) { - fixed (void* ptr = &MemoryMarshal.GetReference(block)) - { - //Calls memset - Unsafe.InitBlock(ptr, 0, (uint)(block.Length * sizeof(T))); - } + //Calls memset + Unsafe.InitBlock(ptr, 0, byteSize); } } } - + /// /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function /// @@ -163,15 +208,19 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void UnsafeZeroMemory(ReadOnlyMemory block) where T : unmanaged { - if (!block.IsEmpty) + if (block.IsEmpty) { - checked - { - //Pin memory and get pointer - using MemoryHandle handle = block.Pin(); - //Calls memset - Unsafe.InitBlock(handle.Pointer, 0, (uint)(block.Length * sizeof(T))); - } + return; + } + + uint byteSize = ByteCount((uint)block.Length); + + checked + { + //Pin memory and get pointer + using MemoryHandle handle = block.Pin(); + //Calls memset + Unsafe.InitBlock(handle.Pointer, 0, byteSize); } } diff --git a/lib/Utils/src/Memory/RpMallocPrivateHeap.cs b/lib/Utils/src/Memory/RpMallocPrivateHeap.cs index f9b7db6..0af91a0 100644 --- a/lib/Utils/src/Memory/RpMallocPrivateHeap.cs +++ b/lib/Utils/src/Memory/RpMallocPrivateHeap.cs @@ -226,7 +226,7 @@ namespace VNLib.Utils.Memory /// Initializes a new RpMalloc first class heap to allocate memory blocks from /// /// A global flag to zero all blocks of memory allocated - /// + /// public RpMallocPrivateHeap(bool zeroAll):base(zeroAll, true) { //Alloc the heap diff --git a/lib/Utils/src/Memory/Win32PrivateHeap.cs b/lib/Utils/src/Memory/Win32PrivateHeap.cs index fe214f4..bdf22f9 100644 --- a/lib/Utils/src/Memory/Win32PrivateHeap.cs +++ b/lib/Utils/src/Memory/Win32PrivateHeap.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Utils -* File: PrivateHeap.cs +* File: Win32PrivateHeap.cs * -* PrivateHeap.cs is part of VNLib.Utils which is part of the larger +* Win32PrivateHeap.cs is part of VNLib.Utils which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Utils is free software: you can redistribute it and/or modify diff --git a/lib/Utils/tests/.runsettings b/lib/Utils/tests/.runsettings new file mode 100644 index 0000000..a4d0377 --- /dev/null +++ b/lib/Utils/tests/.runsettings @@ -0,0 +1,8 @@ + + + + + 1 + + + \ No newline at end of file diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index 34dbb60..2ef8a41 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -42,7 +42,7 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException(() => Shared.Alloc(-1)); //Make sure over-alloc throws - Assert.ThrowsException(() => Shared.Alloc(nuint.MaxValue, false)); + Assert.ThrowsException(() => Shared.Alloc(nuint.MaxValue, false)); } #if TARGET_64_BIT [TestMethod] diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index fb3700e..d55817c 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography; using Microsoft.VisualStudio.TestTools.UnitTesting; using VNLib.Utils.Extensions; +using VNLib.Utils.Memory.Diagnostics; namespace VNLib.Utils.Memory.Tests { @@ -243,7 +244,7 @@ namespace VNLib.Utils.Memory.Tests //Test pinned outsie handle size Assert.ThrowsException(() => _ = handle.Pin(1024)); } - + //Negative value Assert.ThrowsException(() => _ = MemoryUtil.SafeAlloc(-1)); @@ -329,5 +330,79 @@ namespace VNLib.Utils.Memory.Tests //Free struct MemoryUtil.Shared.StructFree(s); } + + [TestMethod()] + public void GetSharedHeapStatsTest() + { + //Confirm heap diagnostics are enabled + Assert.AreEqual(Environment.GetEnvironmentVariable(MemoryUtil.SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV), "1"); + + //Get current stats + HeapStatistics preTest = MemoryUtil.GetSharedHeapStats(); + + //Alloc block + using IMemoryHandle handle = MemoryUtil.Shared.Alloc(1024); + + //Get stats + HeapStatistics postTest = MemoryUtil.GetSharedHeapStats(); + + Assert.IsTrue(postTest.AllocatedBytes == preTest.AllocatedBytes + 1024); + Assert.IsTrue(postTest.AllocatedBlocks == preTest.AllocatedBlocks + 1); + + //Free block + handle.Dispose(); + + //Get stats + HeapStatistics postFree = MemoryUtil.GetSharedHeapStats(); + + //Confirm stats are back to pre test + Assert.IsTrue(preTest.AllocatedBytes == postFree.AllocatedBytes); + Assert.IsTrue(preTest.AllocatedBlocks == postFree.AllocatedBlocks); + } + + [TestMethod()] + public void DiagnosticsHeapWraperTest() + { + //Get a fresh heap + IUnmangedHeap heap = MemoryUtil.InitializeNewHeapForProcess(); + + //Init wrapper and dispose + using TrackedHeapWrapper wrapper = new(heap); + + //Confirm 0 stats + HeapStatistics preTest = wrapper.GetCurrentStats(); + + Assert.IsTrue(preTest.AllocatedBytes == 0); + Assert.IsTrue(preTest.AllocatedBlocks == 0); + Assert.IsTrue(preTest.MaxHeapSize == 0); + Assert.IsTrue(preTest.MaxBlockSize == 0); + Assert.IsTrue(preTest.MinBlockSize == ulong.MaxValue); + + //Alloc a test block + using IMemoryHandle handle = wrapper.Alloc(1024); + + //Get stats + HeapStatistics postTest = wrapper.GetCurrentStats(); + + //Confirm stats represent a single block + Assert.IsTrue(postTest.AllocatedBytes == 1024); + Assert.IsTrue(postTest.AllocatedBlocks == 1); + Assert.IsTrue(postTest.MaxHeapSize == 1024); + Assert.IsTrue(postTest.MaxBlockSize == 1024); + Assert.IsTrue(postTest.MinBlockSize == 1024); + + //Free the block + handle.Dispose(); + + //Get stats + HeapStatistics postFree = wrapper.GetCurrentStats(); + + //Confirm stats are back to 0, or represent the single block + Assert.IsTrue(postFree.AllocatedBytes == 0); + Assert.IsTrue(postFree.AllocatedBlocks == 0); + Assert.IsTrue(postFree.MaxHeapSize == 1024); + Assert.IsTrue(postFree.MaxBlockSize == 1024); + Assert.IsTrue(postFree.MinBlockSize == 1024); + } } } \ No newline at end of file diff --git a/lib/Utils/tests/Memory/VnTableTests.cs b/lib/Utils/tests/Memory/VnTableTests.cs index c9f99ea..6f9fbf2 100644 --- a/lib/Utils/tests/Memory/VnTableTests.cs +++ b/lib/Utils/tests/Memory/VnTableTests.cs @@ -53,9 +53,9 @@ namespace VNLib.Utils.Memory.Tests //Test oom, should be native - Assert.ThrowsException(() => + Assert.ThrowsException(() => { - using VnTable table = new(int.MaxValue, 2); + using VnTable table = new(uint.MaxValue, 3); }); } -- cgit