aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-18 22:43:14 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-18 22:43:14 -0500
commit46caaac9debdaad496c07af9d3806e67a447066c (patch)
tree750bebaa5f3b59f06792b99de8a6fae995452bca /lib
parent52b8e30437e235817ed534dec860e781bb0468c0 (diff)
Heap diag and plugin log file names
Diffstat (limited to 'lib')
-rw-r--r--lib/Net.Http/src/IHeaderCollection.cs4
-rw-r--r--lib/Net.Messaging.FBM/src/Client/Helpers.cs5
-rw-r--r--lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs3
-rw-r--r--lib/Plugins.PluginBase/src/PluginBase.cs10
-rw-r--r--lib/Utils/README.md3
-rw-r--r--lib/Utils/src/Extensions/TimerExtensions.cs4
-rw-r--r--lib/Utils/src/Extensions/TimerResumeHandle.cs (renamed from lib/Utils/src/Extensions/TimerResetHandle.cs)16
-rw-r--r--lib/Utils/src/Memory/Diagnostics/HeapDiagnosticException.cs43
-rw-r--r--lib/Utils/src/Memory/Diagnostics/HeapStatistics.cs76
-rw-r--r--lib/Utils/src/Memory/Diagnostics/IllegalHeapOperationException.cs43
-rw-r--r--lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs178
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs125
-rw-r--r--lib/Utils/src/Memory/RpMallocPrivateHeap.cs2
-rw-r--r--lib/Utils/src/Memory/Win32PrivateHeap.cs4
-rw-r--r--lib/Utils/tests/.runsettings8
-rw-r--r--lib/Utils/tests/Memory/MemoryHandleTest.cs2
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs77
-rw-r--r--lib/Utils/tests/Memory/VnTableTests.cs4
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);
});
}