Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Code path performance monitoring tool.

[<img src="https://img.shields.io/badge/-GitHub-blue.svg?logo=github" />](https://github.com/aka-nse/PathBench/)

[<img src="https://img.shields.io/badge/-NuGet-019733.svg?logo=nuget" />](https://www.nuget.org/packages/akanse.PathBench/)

----

## Features
Expand Down Expand Up @@ -95,3 +99,7 @@ static class SampleClass
### v1.0.0

- First release

### v1.1.0

- Adds support for net standard 2.0
16 changes: 8 additions & 8 deletions src/PathBench/MethodProfileReportFormatter.Graphviz.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand All @@ -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");
Expand Down
14 changes: 12 additions & 2 deletions src/PathBench/PathBench.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<!--<TargetFramework>net10.0</TargetFramework>-->
<TargetFrameworks>net10.0;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

<!-- package configs -->
<PropertyGroup>
<PackageId>akanse.$(AssemblyName)</PackageId>
<Title>PathBench</Title>
<Version>1.0.0</Version>
<Version>1.1.0</Version>
<Company />
<Product>PathBench</Product>
<Description>Code path performance monitoring tool.</Description>
Expand All @@ -21,7 +22,16 @@
</PropertyGroup>

<ItemGroup>
<None Include="../../resources/GraphvizSample.svg" Pack="true" PackagePath="resources" />
<None Include="../../readme.md" Pack="true" PackagePath="" />
<None Include="../../LICENSE.txt" Pack="true" PackagePath="" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0'">
<PackageReference Include="System.Collections.Immutable" Version="10.0.9" />
<PackageReference Include="Microsoft.Bcl.TimeProvider" Version="10.0.9" />
</ItemGroup>

<ItemGroup>
</ItemGroup>
</Project>
14 changes: 12 additions & 2 deletions src/PathBench/PreciseDuration.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#if NET7_0_OR_GREATER
using System.Numerics;
#endif
using System.Text.RegularExpressions;

namespace PathBench;
Expand All @@ -9,9 +11,11 @@ namespace PathBench;
public readonly partial struct PreciseDuration(long ticks)
: IEquatable<PreciseDuration>
, IComparable<PreciseDuration>
#if NET7_0_OR_GREATER
, IComparisonOperators<PreciseDuration, PreciseDuration, bool>
, IAdditionOperators<PreciseDuration, PreciseDuration, PreciseDuration>
, ISubtractionOperators<PreciseDuration, PreciseDuration, PreciseDuration>
#endif
, IFormattable
{
/// <summary>
Expand Down Expand Up @@ -202,8 +206,14 @@ public static PreciseDuration FromNanoseconds(double nanoseconds) =>

#region ToString overloads

[GeneratedRegex(@"^(?<real>.+?)(?<unit>nsec|usec|msec|sec|)$")]
//lang=regex
private const string _formatMatchingPattern = @"^(?<real>.+?)(?<unit>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();

/// <inheritdoc />
Expand Down Expand Up @@ -279,7 +289,7 @@ public string ToString(string? realPartFormat, TimeScale timeScale, IFormatProvi
_ => throw new FormatException("Invalid time scale."),
};

#endregion ToString overloads
#endregion ToString overloads

/// <inheritdoc />
[ExcludeFromCodeCoverage]
Expand Down
111 changes: 111 additions & 0 deletions src/PathBench/__FrameworkCompatibility.cs
Original file line number Diff line number Diff line change
@@ -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<T>
{
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<T> AsSpan<T>(List<T>? list)
{
Span<T> span = default;
if (list is not null)
{
var listCompat = Unsafe.As<List<T>, ListCompat<T>>(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<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
public bool TryAdd(TKey key, TValue value)
{
if (dict.ContainsKey(key)) return false;
dict.Add(key, value);
return true;
}
}

extension<TKey, TValue>(KeyValuePair<TKey, TValue> 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
26 changes: 26 additions & 0 deletions tests/PathBench.Test/MethodProfileReportFormatter.GraphvizTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Reflection;
using System.Runtime.CompilerServices;

namespace PathBench.Test;
Expand All @@ -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? _,
Expand All @@ -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<string, string> SanitizeIdentifierTestCases() =>
Expand Down
2 changes: 1 addition & 1 deletion tests/PathBench.Test/PathBench.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand Down
Loading