Dot.Tick
1.0.0
dotnet add package Dot.Tick --version 1.0.0
NuGet\Install-Package Dot.Tick -Version 1.0.0
<PackageReference Include="Dot.Tick" Version="1.0.0" />
<PackageVersion Include="Dot.Tick" Version="1.0.0" />
<PackageReference Include="Dot.Tick" />
paket add Dot.Tick --version 1.0.0
#r "nuget: Dot.Tick, 1.0.0"
#:package Dot.Tick@1.0.0
#addin nuget:?package=Dot.Tick&version=1.0.0
#tool nuget:?package=Dot.Tick&version=1.0.0
Dot.Tick
Lightweight method timing for .NET using DispatchProxy. Add [Timed]
to your implementation methods and get simple, precise timing logs in DEBUG builds with near‑zero overhead in RELEASE.
- Zero impact in Release:
[Timed]
isConditional("DEBUG")
and is not emitted in Release builds. - Works with sync, Task, Task<T>, ValueTask, and ValueTask<T>.
- Pluggable logger and threshold filtering (
IElapsedLogger
,GlobalThreshold
). - Simple API: wrap your interface implementation with
DotTickProxy.Create<T>(impl)
.
Why Dot.Tick?
- You often want basic execution time visibility during development without pulling in heavy AOP frameworks.
- You want a no‑risk Release build: when you ship, the attribute is omitted, so timing code disappears.
- You need async coverage: Tasks and ValueTasks are awaited and measured correctly.
Requirements
- .NET 9.0
- Methods must live on a type that implements an interface. You create a proxy for that interface and call through it.
Install
Install from NuGet:
PM> Install-Package Dot.Tick
# or
> dotnet add package Dot.Tick
Quick start
public interface ICalculator
{
int Add(int a, int b);
Task<int> DelayAddAsync(int a, int b, int delayMs);
}
public class Calculator : ICalculator
{
[Timed]
public int Add(int a, int b) => a + b;
[Timed]
public async Task<int> DelayAddAsync(int a, int b, int delayMs)
{
await Task.Delay(delayMs);
return a + b;
}
}
var calc = DotTickProxy.Create<ICalculator>(new Calculator());
var sum = await calc.DelayAddAsync(1, 2, 10);
In DEBUG builds you will see console output like:
[Dot.Tick] Namespace.Calculator.DelayAddAsync took 10.123 ms
Failures include a suffix:
[Dot.Tick] Namespace.Calculator.FailSync took 0.642 ms (failed)
ASP.NET Core (DI) example
Register a proxied service in your service container:
builder.Services.AddSingleton<ICalculator>(sp => DotTickProxy.Create<ICalculator>(new Calculator()));
Then inject and use ICalculator
as usual in your endpoints/controllers. In DEBUG, timed methods will log to the console output.
Configuration
You can configure Dot.Tick globally. Defaults: enabled in DEBUG, console logger, no threshold.
DotTick.Configure(o =>
{
o.GlobalThreshold = TimeSpan.FromMilliseconds(5); // only log when >= 5ms
o.Logger = new MyStructuredLogger(); // plug your own logger
o.Enabled = true; // enable/disable at runtime (DEBUG only)
});
Implement your own logger by implementing IElapsedLogger
:
public sealed class MyStructuredLogger : IElapsedLogger
{
public void Log(string typeName, string methodName, TimeSpan elapsed, bool failed)
{
// Route to Serilog / ILogger / Application Insights, etc.
Console.WriteLine($"[Dot.Tick] {typeName}.{methodName} took {elapsed.TotalMilliseconds:F3} ms {(failed ? "(failed)" : "")}");
}
}
ValueTask support
ValueTask
and ValueTask<T>
are awaited and measured correctly:
public interface IAsyncCalc
{
ValueTask DelayNoResultAsync(int ms);
ValueTask<int> DelayAddValueTaskAsync(int a, int b, int ms);
}
public class AsyncCalc : IAsyncCalc
{
[Timed]
public async ValueTask DelayNoResultAsync(int ms) => await Task.Delay(ms);
[Timed]
public async ValueTask<int> DelayAddValueTaskAsync(int a, int b, int ms)
{
await Task.Delay(ms);
return a + b;
}
}
var calc = DotTickProxy.Create<IAsyncCalc>(new AsyncCalc());
await calc.DelayNoResultAsync(5);
var sum = await calc.DelayAddValueTaskAsync(3, 7, 5);
How it works
- Dot.Tick uses
DispatchProxy
to create a proxy for your interface. - In DEBUG, when the target implementation method is marked with
[Timed]
, Dot.Tick starts a stopwatch and logs on completion or exception. - In RELEASE,
[Timed]
is compiled out entirely, so there is no attribute to detect; the proxy simply calls through with negligible overhead. - Method attribute lookups are cached to reduce reflection cost.
Notes & limitations
[Timed]
is effective only in DEBUG builds. You can still disable logging in DEBUG at runtime viaDotTick.Configure
.- Apply
[Timed]
on the concrete implementation method, not on the interface. - You must call through the proxy returned by
DotTickProxy.Create<T>()
; calling the raw implementation bypasses timing. - Only interface proxies are supported (this is a
DispatchProxy
limitation). GlobalThreshold
filters out fast calls; set toTimeSpan.Zero
to log everything.
Demos and tests
- Console demo:
Demo/Dot.Tick.Demo.Console
- Web API demo:
Demo/Dot.Tick.Demo.WebApi
- Unit tests:
Dot.Tick.Tests
(show DEBUG/RELEASE differences in logging)
Troubleshooting
- I see no logs: Make sure you are running a DEBUG build and that you are calling through the proxy, not the raw implementation.
- My method is not timed: Ensure the implementation method has
[Timed]
and that the method is part of the proxied interface. - I only want slower calls: Set
GlobalThreshold
to a non‑zero value.
License
MIT
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.