diff options
author | vnugent <public@vaughnnugent.com> | 2023-01-18 22:43:14 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-01-18 22:43:14 -0500 |
commit | 46caaac9debdaad496c07af9d3806e67a447066c (patch) | |
tree | 750bebaa5f3b59f06792b99de8a6fae995452bca /lib | |
parent | 52b8e30437e235817ed534dec860e781bb0468c0 (diff) |
Heap diag and plugin log file names
Diffstat (limited to 'lib')
18 files changed, 547 insertions, 60 deletions
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); /// <summary> - /// Overwrites (sets) the given response header to the exact value specified + /// Appends the header value to the current header value /// </summary> /// <param name="header">The enumrated header id</param> /// <param name="value">The value to specify</param> void Append(HttpResponseHeader header, string? value); /// <summary> - /// Overwrites (sets) the given response header to the exact value specified + /// Appends the header value to the current header value /// </summary> /// <param name="header">The header name</param> /// <param name="value">The value to specify</param> 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 /// </summary> public static int RandomMessageId => RandomNumberGenerator.GetInt32(1, int.MaxValue); + /// <summary> /// Gets the remaining data after the current position of the stream. /// </summary> @@ -197,7 +198,7 @@ namespace VNLib.Net.Messaging.FBM /// <param name="header">The <see cref="HeaderCommand"/> of the header</param> /// <param name="value">The value of the header</param> /// <param name="encoding">Encoding to use when writing character message</param> - /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> public static void WriteHeader(ref this ForwardOnlyWriter<byte> buffer, byte header, ReadOnlySpan<char> 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<char>)passHash, (ReadOnlySpan<char>)password); } + /// <summary> /// Verifies a password against its previously encoded hash. /// </summary> @@ -116,6 +117,7 @@ namespace VNLib.Plugins.Essentials.Accounts MemoryUtil.InitializeBlock(secretBuffer.Span); } } + /// <summary> /// Verifies a password against its hash. Partially exposes the Argon2 api. /// </summary> @@ -203,6 +205,7 @@ namespace VNLib.Plugins.Essentials.Accounts MemoryUtil.InitializeBlock(buffer.Span); } } + /// <summary> /// 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<RollingInterval>(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 /// <param name="timer"></param> /// <param name="resumeTime"><see cref="TimeSpan"/> representing the amount of time the timer should wait before invoking the callback function</param> /// <returns>A new <see cref="OpenHandle"/> if the timer was stopped successfully that will resume the timer when closed, null otherwise</returns> - 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; } /// <summary> diff --git a/lib/Utils/src/Extensions/TimerResetHandle.cs b/lib/Utils/src/Extensions/TimerResumeHandle.cs index 57a71e8..f386974 100644 --- a/lib/Utils/src/Extensions/TimerResetHandle.cs +++ b/lib/Utils/src/Extensions/TimerResumeHandle.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Utils -* File: TimerExtensions.cs +* File: TimerResetHandle.cs * -* TimerExtensions.cs is part of VNLib.Utils which is part of the larger +* 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 @@ -31,12 +31,12 @@ 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 /// </summary> - public readonly struct TimerResetHandle: IEquatable<TimerResetHandle>, IDisposable + public readonly struct TimerResumeHandle: IEquatable<TimerResumeHandle>, IDisposable { private readonly Timer _timer; private readonly TimeSpan _resumeTime; - internal TimerResetHandle(Timer timer, TimeSpan resumeTime) + internal TimerResumeHandle(Timer timer, TimeSpan resumeTime) { _timer = timer; _resumeTime = resumeTime; @@ -53,14 +53,14 @@ namespace VNLib.Utils.Extensions public void Dispose() => Resume(); ///<inheritdoc/> - public bool Equals(TimerResetHandle other) => _timer.Equals(other) && _resumeTime == other._resumeTime; + public bool Equals(TimerResumeHandle other) => _timer.Equals(other) && _resumeTime == other._resumeTime; ///<inheritdoc/> - public override bool Equals(object? obj) => obj is TimerResetHandle trh && Equals(trh); + public override bool Equals(object? obj) => obj is TimerResumeHandle trh && Equals(trh); ///<inheritdoc/> public override int GetHashCode() => _timer.GetHashCode() + _resumeTime.GetHashCode(); ///<inheritdoc/> - public static bool operator ==(TimerResetHandle left, TimerResetHandle right) => left.Equals(right); + public static bool operator ==(TimerResumeHandle left, TimerResumeHandle right) => left.Equals(right); ///<inheritdoc/> - public static bool operator !=(TimerResetHandle left, TimerResetHandle right) => !(left == 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 +{ + /// <summary> + /// A base class for heap memory diagnostics exceptions + /// </summary> + 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 +{ + /// <summary> + /// A structures that represents the current/last captures + /// statistics of the monitored heap + /// </summary> + public readonly struct HeapStatistics : IEquatable<HeapStatistics> + { + /// <summary> + /// The current size (in bytes) of the heap + /// </summary> + public readonly ulong AllocatedBytes { get; init; } + /// <summary> + /// The largest block size seen + /// </summary> + public readonly ulong MaxBlockSize { get; init; } + /// <summary> + /// The largest size of the heap, in bytes. + /// </summary> + public readonly ulong MaxHeapSize { get; init; } + /// <summary> + /// The smallest block size seen allocated + /// </summary> + public readonly ulong MinBlockSize { get; init; } + /// <summary> + /// The number of allocated handles/blocks + /// </summary> + public readonly ulong AllocatedBlocks { get; init; } + + ///<inheritdoc/> + public override bool Equals(object? obj) => obj is HeapStatistics s && Equals(s); + + ///<inheritdoc/> + public override int GetHashCode() => (int)((AllocatedBytes ^ MaxBlockSize) & int.MaxValue); + + ///<inheritdoc/> + public static bool operator ==(HeapStatistics left, HeapStatistics right) => left.Equals(right); + ///<inheritdoc/> + public static bool operator !=(HeapStatistics left, HeapStatistics right) => !(left == right); + + ///<inheritdoc/> + 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 +{ + /// <summary> + /// An exception that represents an illegal memory operation during heap monitoring + /// </summary> + 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 +{ + /// <summary> + /// A wrapper for <see cref="IUnmangedHeap"/> that tracks + /// statistics for memory allocations. + /// </summary> + public class TrackedHeapWrapper : VnDisposeable, IUnmangedHeap + { + private readonly IUnmangedHeap _heap; + private readonly object _statsLock; + private readonly ConcurrentDictionary<IntPtr, ulong> _table; + + /// <summary> + /// Gets the underlying heap + /// </summary> + protected IUnmangedHeap Heap => _heap; + + /// <summary> + /// Gets the internal lock held when updating statistics + /// during allocations or frees + /// </summary> + protected object StatsLock => _statsLock; + + /* Stats block */ + private ulong _alloctedBytes; + private ulong _maxBlockSize; + private ulong _maxHeapSize; + private ulong _minBlockSize; + + + /// <summary> + /// Creates a new diagnostics wrapper for the heap + /// </summary> + /// <param name="heap">The heap to gather statistics on</param> + public TrackedHeapWrapper(IUnmangedHeap heap) + { + _statsLock = new(); + _table = new(); + _heap = heap; + //Default min block size to 0 + _minBlockSize = ulong.MaxValue; + } + + /// <summary> + /// Captures the current state of the heap. + /// </summary> + /// <returns>A new <see cref="HeapStatistics"/> captured from the current heap</returns> + 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 + }; + } + } + + ///<inheritdoc/> + 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); + } + + ///<inheritdoc/> + protected override void Free() + { + Heap.Dispose(); + } + + ///<inheritdoc/> + 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; + } + + ///<inheritdoc/> + 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 { + /// <summary> + /// The environment variable name used to specify the shared heap type + /// to create + /// </summary> public const string SHARED_HEAP_TYPE_ENV= "VNLIB_SHARED_HEAP_TYPE"; + /// <summary> + /// When creating a heap that accepts an initial size, this value is passed + /// to it, otherwise no initial heap size is set. + /// </summary> public const string SHARED_HEAP_INTIAL_SIZE_ENV = "VNLIB_SHARED_HEAP_SIZE"; + /// <summary> + /// The environment variable name used to enable share heap diagnostics + /// </summary> + public const string SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV = "VNLIB_SHARED_HEAP_DIAGNOSTICS"; /// <summary> /// Initial shared heap size (bytes) @@ -70,15 +84,21 @@ namespace VNLib.Utils.Memory private static readonly Lazy<IUnmangedHeap> _sharedHeap = InitHeapInternal(); - //Avoiding statit initializer + //Avoiding static initializer private static Lazy<IUnmangedHeap> InitHeapInternal() { - Lazy<IUnmangedHeap> 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<IUnmangedHeap> 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) { @@ -90,47 +110,68 @@ namespace VNLib.Utils.Memory } /// <summary> + /// Gets the shared heap statistics if stats are enabled + /// </summary> + /// <returns> + /// The <see cref="HeapStatistics"/> of the shared heap, or an empty + /// <see cref="HeapStatistics"/> if diagnostics are not enabled. + /// </returns> + 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(); + } + + /// <summary> /// Initializes a new <see cref="IUnmangedHeap"/> determined by compilation/runtime flags /// and operating system type for the current proccess. /// </summary> /// <returns>An <see cref="IUnmangedHeap"/> for the current process</returns> /// <exception cref="SystemException"></exception> /// <exception cref="DllNotFoundException"></exception> - 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; } /// <summary> /// Gets a value that indicates if the Rpmalloc native library is loaded /// </summary> - 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<T>(ReadOnlySpan<T> block) where T : unmanaged { - if (!block.IsEmpty) + if (block.IsEmpty) + { + return; + } + + uint byteSize = ByteCount<T>((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); } } } - + /// <summary> /// Zeros a block of memory of umanged type. If Windows is detected at runtime, calls RtlSecureZeroMemory Win32 function /// </summary> @@ -163,15 +208,19 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void UnsafeZeroMemory<T>(ReadOnlyMemory<T> 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<T>((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 /// </summary> /// <param name="zeroAll">A global flag to zero all blocks of memory allocated</param> - /// <exception cref="NativeMemoryOutOfMemoryException"></exception> + /// <exception cref="NativeMemoryException"></exception> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<RunSettings> + <RunConfiguration> + <EnvironmentVariables> + <VNLIB_SHARED_HEAP_DIAGNOSTICS>1</VNLIB_SHARED_HEAP_DIAGNOSTICS> + </EnvironmentVariables> + </RunConfiguration> +</RunSettings>
\ 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<ArgumentOutOfRangeException>(() => Shared.Alloc<byte>(-1)); //Make sure over-alloc throws - Assert.ThrowsException<NativeMemoryOutOfMemoryException>(() => Shared.Alloc<byte>(nuint.MaxValue, false)); + Assert.ThrowsException<OutOfMemoryException>(() => Shared.Alloc<byte>(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<ArgumentOutOfRangeException>(() => _ = handle.Pin(1024)); } - + //Negative value Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.SafeAlloc<byte>(-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<string?>(Environment.GetEnvironmentVariable(MemoryUtil.SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV), "1"); + + //Get current stats + HeapStatistics preTest = MemoryUtil.GetSharedHeapStats(); + + //Alloc block + using IMemoryHandle<byte> handle = MemoryUtil.Shared.Alloc<byte>(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<byte> handle = wrapper.Alloc<byte>(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<NativeMemoryOutOfMemoryException>(() => + Assert.ThrowsException<OutOfMemoryException>(() => { - using VnTable<int> table = new(int.MaxValue, 2); + using VnTable<int> table = new(uint.MaxValue, 3); }); } |