diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..a38b718 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,65 @@ +# Copilot Instructions for PathBench + +## What's this file for? + +- This file provides instructions for generating source or operating as an agent + for this repository to GitHub Copilot or other AI code assistants. +- When implementing new features, please use the technology selection, + design policy, and module configuration shown here as a premise. +- If in doubt, explore the files in the repository and ask the user "Is this + what you meant?" + +## Preconditions + +- The comments or documentations in the source codes must be in English unless + there is specific instructions. +- The answer in the shell writes in user local language as possible as. +- When making code changes, if the number of lines modified is likely to exceed + 200, please prompt the user beforehand with the message: "This instruction may + result in over 200 lines of code being modified. Do you wish to proceed?" +- When making significant changes, first create a plan outlining what you intend + to do, then propose it to the user by saying, "We plan to proceed with this + approach." If the user requests modifications to the plan at this stage, + revise the plan accordingly and resubmit it. + +## Project overview + +PathBench is a .NET library for benchmarking and analyzing the performance of +different algorithms and data structures. It provides a way to measure execution +time, allowing developers to optimize their code effectively. + +Please refer to the [README.md](../README.md) for more details about the +project, including its features, installation instructions, and usage examples. + +## Directories and files + +This project has the following directory structure: + +``` +./ ++-- .editorconfig # Editor configuration file ++-- .gitignore # Git ignore file ++-- Directory.Build.props # MSBuild shared properties for the project ++-- LICENSE.txt # License information for the project ++-- Measure-Coverage.ps1 # PowerShell script for measuring code coverage ++-- PathBench.slnx # Visual Studio solution file ++-- README.md # Project overview and documentation ++-- global.json # .NET SDK version configuration ++-- .github/ +| +-- workflows/ # GitHub Actions workflow files +| | +-- dotnet-CI.yml # CI workflow for .NET builds and tests +| +-- copilot-instructions.md # This file: instructions for GitHub Copilot ++-- resources/ # Misc. resource files for the project ++-- src/ # Source code for the library +| +-- PathBench/ # Main library code ++ tests/ # Unit tests for the library + +-- PathBench.Test/ # Unit tests for the library + +-- etc/ + +-- coverlet.runsettings # Coverlet configuration for code coverage analysis +``` + +## Coding style + +- Unless specific commented, the coding style should follow .editorconfig file. +- Unless specific commented, the coding style should follow the [Microsoft C# coding conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions). +- All of public members must have XML documentation comments. diff --git a/README.md b/README.md index c18cac7..093521f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Code path performance monitoring tool. +[](https://github.com/aka-nse/PathBench/) + +[](https://www.nuget.org/packages/akanse.PathBench/) + ---- ## Features @@ -95,3 +99,7 @@ static class SampleClass ### v1.0.0 - First release + +### v1.1.0 + +- Adds support for net standard 2.0 diff --git a/src/PathBench/MethodProfileReportFormatter.Graphviz.cs b/src/PathBench/MethodProfileReportFormatter.Graphviz.cs index 1962dcc..3d72fb3 100644 --- a/src/PathBench/MethodProfileReportFormatter.Graphviz.cs +++ b/src/PathBench/MethodProfileReportFormatter.Graphviz.cs @@ -86,17 +86,17 @@ public static string SanitizeIdentifier(string name) { var sb = new StringBuilder(); sb.Append("__"); - foreach (var c in name.EnumerateRunes()) + foreach (var c in name) { - switch (c.Value) + switch (c) { - case >= 0x30 and < 0x3A: - case >= 0x41 and < 0x5B: - case >= 0x61 and < 0x7B: + case >= (char)0x30 and < (char)0x3A: + case >= (char)0x41 and < (char)0x5B: + case >= (char)0x61 and < (char)0x7B: sb.Append(c); continue; default: - sb.Append($"_u{(uint)c.Value:X04}_"); + sb.Append($"_u{(uint)c:X04}_"); continue; } } @@ -106,9 +106,9 @@ public static string SanitizeIdentifier(string name) public static string SanitizeLabel(string name) { var sb = new StringBuilder(name.Length * 2); - foreach (var c in name.EnumerateRunes()) + foreach (var c in name) { - switch (c.Value) + switch (c) { case '\n': sb.Append(@"\n"); diff --git a/src/PathBench/PathBench.csproj b/src/PathBench/PathBench.csproj index d33b87c..d2eecea 100644 --- a/src/PathBench/PathBench.csproj +++ b/src/PathBench/PathBench.csproj @@ -1,7 +1,8 @@  - net10.0 + + net10.0;netstandard2.0 True @@ -9,7 +10,7 @@ akanse.$(AssemblyName) PathBench - 1.0.0 + 1.1.0 PathBench Code path performance monitoring tool. @@ -21,7 +22,16 @@ + + + + + + + + + diff --git a/src/PathBench/PreciseDuration.cs b/src/PathBench/PreciseDuration.cs index 135be56..c344e32 100644 --- a/src/PathBench/PreciseDuration.cs +++ b/src/PathBench/PreciseDuration.cs @@ -1,4 +1,6 @@ +#if NET7_0_OR_GREATER using System.Numerics; +#endif using System.Text.RegularExpressions; namespace PathBench; @@ -9,9 +11,11 @@ namespace PathBench; public readonly partial struct PreciseDuration(long ticks) : IEquatable , IComparable +#if NET7_0_OR_GREATER , IComparisonOperators , IAdditionOperators , ISubtractionOperators +#endif , IFormattable { /// @@ -202,8 +206,14 @@ public static PreciseDuration FromNanoseconds(double nanoseconds) => #region ToString overloads - [GeneratedRegex(@"^(?.+?)(?nsec|usec|msec|sec|)$")] + //lang=regex + private const string _formatMatchingPattern = @"^(?.+?)(?nsec|usec|msec|sec|)$"; +#if NET7_0_OR_GREATER + [GeneratedRegex(_formatMatchingPattern)] private static partial Regex GetFormatMatcher(); +#else + private static Regex GetFormatMatcher() => new (_formatMatchingPattern, RegexOptions.Compiled); +#endif private static readonly Regex _FormatMatcher = GetFormatMatcher(); /// @@ -279,7 +289,7 @@ public string ToString(string? realPartFormat, TimeScale timeScale, IFormatProvi _ => throw new FormatException("Invalid time scale."), }; - #endregion ToString overloads +#endregion ToString overloads /// [ExcludeFromCodeCoverage] diff --git a/src/PathBench/__FrameworkCompatibility.cs b/src/PathBench/__FrameworkCompatibility.cs new file mode 100644 index 0000000..5283430 --- /dev/null +++ b/src/PathBench/__FrameworkCompatibility.cs @@ -0,0 +1,111 @@ +#pragma warning disable IDE0079 // Remove unnecessary suppression +#pragma warning disable IDE0130 // Namespace does not match folder structure + +#if !NET5_0_OR_GREATER +using System.Runtime.CompilerServices; + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit; +} + +namespace System.Runtime.InteropServices +{ + internal static class CollectionsMarshal + { + private sealed class ListCompat + { + internal T[]? _items = default; // Do not rename (binary serialization) + internal int _size = default; // Do not rename (binary serialization) + internal int _version = default; // Do not rename (binary serialization) + } + + public static ReadOnlySpan AsSpan(List? list) + { + Span span = default; + if (list is not null) + { + var listCompat = Unsafe.As, ListCompat>(ref list); + int size = listCompat._size; + T[] items = listCompat._items!; + + if ((uint)size > (uint)items.Length) + { + throw new InvalidOperationException(); + } + + span = items.AsSpan(0, size); + } + + return span; + } + } +} +#endif + +#if !NET7_0_OR_GREATER +namespace System +{ + internal static class TimeSpanExtensions + { + extension(TimeSpan ts) + { + public static long TicksPerMicrosecond => TimeSpan.TicksPerMillisecond / 1000; + } + } +} +#endif + +#if !NET9_0_OR_GREATER +namespace System.Threading +{ + internal class Lock + { + private sealed class Scope(object token) : IDisposable + { + public void Dispose() + { + Monitor.Exit(token); + } + } + + private readonly object _token = new(); + + internal IDisposable EnterScope() + { + Monitor.Enter(_token); + return new Scope(_token); + } + } +} +#endif + +#if !NETSTANDARD2_1_OR_GREATER && !NETCOREAPP2_0_OR_GREATER +namespace System.Collections.Generic +{ + internal static class CollectionExtensions + { + extension(IDictionary dict) + { + public bool TryAdd(TKey key, TValue value) + { + if (dict.ContainsKey(key)) return false; + dict.Add(key, value); + return true; + } + } + + extension(KeyValuePair kv) + { + public void Deconstruct(out TKey key, out TValue value) + { + key = kv.Key; + value = kv.Value; + } + } + } +} +#endif + +#pragma warning restore IDE0130 // Namespace does not match folder structure +#pragma warning restore IDE0079 // Remove unnecessary suppression \ No newline at end of file diff --git a/tests/PathBench.Test/MethodProfileReportFormatter.GraphvizTest.cs b/tests/PathBench.Test/MethodProfileReportFormatter.GraphvizTest.cs index 3a461ff..dabba76 100644 --- a/tests/PathBench.Test/MethodProfileReportFormatter.GraphvizTest.cs +++ b/tests/PathBench.Test/MethodProfileReportFormatter.GraphvizTest.cs @@ -1,3 +1,4 @@ +using System.Reflection; using System.Runtime.CompilerServices; namespace PathBench.Test; @@ -9,6 +10,8 @@ private static class PrivateAccess public const string _typeName = $"{nameof(PathBench)}.{nameof(MethodProfileReportFormatter)}+GraphvizStyle_, PathBench"; +#if NET10_0_OR_GREATER + [UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "SanitizeIdentifier")] public extern static string SanitizeIdentifier( [UnsafeAccessorType(_typeName)] object? _, @@ -18,6 +21,29 @@ public extern static string SanitizeIdentifier( public extern static string SanitizeLabel( [UnsafeAccessorType(_typeName)] object? _, string input); + +#else + private static readonly Type _GraphvizStyle_ = + typeof(MethodProfileReportFormatter) + .GetNestedType("GraphvizStyle_", BindingFlags.NonPublic)!; + + private static readonly MethodInfo _SanitizeIdentifier = + _GraphvizStyle_.GetMethod("SanitizeIdentifier", BindingFlags.Public | BindingFlags.Static)!; + + private static readonly MethodInfo _SanitizeLabel = + _GraphvizStyle_.GetMethod("SanitizeLabel", BindingFlags.Public | BindingFlags.Static)!; + + public static string SanitizeIdentifier(object? _, string input) + { + return (string)_SanitizeIdentifier.Invoke(null, [input])!; + } + + public static string SanitizeLabel(object? _, string input) + { + return (string)_SanitizeLabel.Invoke(null, [input])!; + } + +#endif } public static TheoryData SanitizeIdentifierTestCases() => diff --git a/tests/PathBench.Test/PathBench.Test.csproj b/tests/PathBench.Test/PathBench.Test.csproj index 4abab7a..e3530fb 100644 --- a/tests/PathBench.Test/PathBench.Test.csproj +++ b/tests/PathBench.Test/PathBench.Test.csproj @@ -1,7 +1,7 @@  - net10.0 + net8.0;net10.0 enable enable false