Automatic method profiling with AOP (Castle.DynamicProxy) support for Chronolap. Profile methods using simple attributes.
$ dotnet add package Chronolap.ProfilerAdvanced stopwatch library with lap tracking support for .NET developers.
AddChronolap() extension method)Install via NuGet:
dotnet add package Chronolap
using Chronolap;
using System;
using System.Threading;
class Program
{
static void Main()
{
var timer = new ChronolapTimer();
timer.Start();
Thread.Sleep(100);
timer.Lap("First lap");
Thread.Sleep(200);
timer.Lap("Second lap");
timer.Stop();
foreach (var lap in timer.Laps)
{
Console.WriteLine(lap);
}
}
}
var timer = new ChronolapTimer();
timer.Start();
// Measure and get return value
int result = timer.MeasureExecutionTime(() => CalculateValue(), "Calculation");
Console.WriteLine($"Result: {result}");
// Async measurement with return value
var data = await timer.MeasureExecutionTimeAsync(async () =>
{
return await FetchDataAsync();
}, "DataFetch");
var timer = new ChronolapTimer();
timer.Start();
// Lap is recorded even if exception occurs
try
{
timer.MeasureExecutionTimeWithExceptionHandling(() =>
{
RiskyOperation();
}, "RiskyOperation");
}
catch (Exception ex)
{
// Lap was still recorded
Console.WriteLine($"Operation failed but timing was recorded");
}
// With return value
int? result = null;
try
{
result = timer.MeasureExecutionTimeWithExceptionHandling(() =>
{
return RiskyOperationWithReturn();
}, "RiskyOperation");
}
finally
{
// Timing is always recorded
}
var timer = new ChronolapTimer();
timer.Start();
// Record multiple laps
for (int i = 0; i < 50; i++)
{
Thread.Sleep(10 + i);
timer.Lap($"Lap{i}");
}
// Calculate statistics
var min = timer.CalculateLapStatistic(LapStatisticsType.Min);
var max = timer.CalculateLapStatistic(LapStatisticsType.Max);
var mean = timer.CalculateLapStatistic(LapStatisticsType.ArithmeticMean);
var median = timer.CalculateLapStatistic(LapStatisticsType.Median);
var stdDev = timer.CalculateLapStatistic(LapStatisticsType.StandardDeviation);
var variance = timer.CalculateLapStatistic(LapStatisticsType.Variance);
// Calculate percentiles
var p50 = timer.CalculatePercentile(50); // Median
var p95 = timer.CalculatePercentile(95); // 95th percentile
var p99 = timer.CalculatePercentile(99); // 99th percentile
// Find fastest and slowest laps
var fastest = timer.GetFastestLap();
var slowest = timer.GetSlowestLap();
Console.WriteLine($"Fastest: {fastest?.Name} ({fastest?.Duration.TotalMilliseconds} ms)");
Console.WriteLine($"Slowest: {slowest?.Name} ({slowest?.Duration.TotalMilliseconds} ms)");
// Configure maximum lap count and minimum lap count for statistics
var timer = new ChronolapTimer(
maxLapCount: 5000,
minimumLapCountForStatistics: 50
);
// Or change minimum lap count at runtime
timer.MinimumLapCountForStatistics = 100;
// Access configuration
Console.WriteLine($"Max Lap Count: {timer.MaxLapCount}");
Console.WriteLine($"Min Lap Count for Stats: {timer.MinimumLapCountForStatistics}");
var timer = new ChronolapTimer();
timer.Start();
Thread.Sleep(100);
timer.Lap("Before pause");
timer.Pause();
// Timer is paused, elapsed time won't increase
Thread.Sleep(1000); // This won't be counted
timer.Resume();
Thread.Sleep(200);
timer.Lap("After resume");
timer.Stop();
Chronolap is fully thread-safe and can be safely used in multi-threaded environments:
var timer = new ChronolapTimer();
timer.Start();
// Multiple threads can safely add laps concurrently
Parallel.For(0, 100, i =>
{
Thread.Sleep(10);
timer.Lap($"Lap{i}");
});
// Statistics can be calculated while other threads are adding laps
var mean = timer.CalculateLapStatistic(LapStatisticsType.ArithmeticMean);
var fastest = timer.GetFastestLap();
timer.Stop();
All public methods and properties are thread-safe, ensuring safe concurrent access from multiple threads.
You can easily integrate Chronolap lap timings with OpenTelemetry Activity by using the provided extension methods. These allow you to add lap duration tags directly to your active tracing activities.
using System.Diagnostics;
using Chronolap;
using Chronolap.OpenTelemetry;
var activitySource = new ActivitySource("MyCompany.MyProduct.MyLibrary");
var timer = new ChronolapTimer();
using (var activity = activitySource.StartActivity("ExampleOperation"))
{
timer.Start();
// Perform some operations
// ...
timer.Lap("Initialization");
// Lap after initialization
timer.Lap("Processing");
// Lap after processing
timer.Stop();
// Add last lap duration as tag
activity.Lap("Processing", timer);
// Or export all lap durations as tags at once
activity.ExportAllLaps(timer);
}
Chronolap provides built-in support for Dependency Injection through the AddChronolap() extension method. This allows you to easily register ChronolapTimer in your service collection and inject it into your classes.
using Chronolap;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
// Register Chronolap with default settings
builder.Services.AddChronolap();
// Or configure options
builder.Services.AddChronolap(options =>
{
options.MaxLapCount = 5000;
options.MinimumLapCountForStatistics = 50;
});
var app = builder.Build();
using Chronolap;
using Microsoft.Extensions.Logging;
public class MyService
{
private readonly ChronolapTimer _timer;
private readonly ILogger<MyService> _logger;
public MyService(ChronolapTimer timer, ILogger<MyService> logger)
{
_timer = timer;
_logger = logger;
}
public void DoWork()
{
_timer.Start();
// Your operations here
Thread.Sleep(100);
_timer.Lap("Operation 1");
Thread.Sleep(200);
_timer.Lap("Operation 2");
_timer.Stop();
// Log results using extension method
_logger.LogLaps(_timer, LogLevel.Information);
}
}
For automatic method profiling with attributes and AOP support:
dotnet add package Chronolap.Profiler
Simple Usage:
using Chronolap.Profiler;
using Microsoft.Extensions.DependencyInjection;
// Setup DI
var services = new ServiceCollection();
services.AddLogging();
services.AddChronolapProfiler();
// Create service with interface
services.AddTransient<IDataService, DataService>();
var serviceProvider = services.BuildServiceProvider();
// Use the service - methods with [ChronolapProfile] are automatically profiled!
var dataService = serviceProvider.GetRequiredService<IDataService>();
dataService.ProcessData();
// View results
var profiler = serviceProvider.GetRequiredService<ChronolapProfiler>();
Console.WriteLine(profiler.ExportSummary());
Service with Attributes:
public interface IDataService
{
void ProcessData();
Task<int> CalculateAsync();
}
public class DataService : IDataService
{
[ChronolapProfile("DataProcessing", Category = "Business", Tags = "critical")]
public void ProcessData()
{
// Your code - automatically profiled!
Thread.Sleep(100);
}
[ChronolapProfile("Calculation", Category = "Compute")]
public async Task<int> CalculateAsync()
{
// Async methods are also profiled!
await Task.Delay(50);
return 42;
}
}
Key Features:
[ChronolapProfile] attributeThe AddChronolap() method supports configuration through ChronolapOptions:
MaxLapCount: Maximum number of laps to store (default: 1000)MinimumLapCountForStatistics: Minimum lap count required for statistics calculation (default: 30)If ILogger<ChronolapTimer> is registered in your DI container, ChronolapTimer will automatically use it for logging lap information.
Contributions are welcome! Please open issues or pull requests.
Thread Safety:
New Statistics Features:
Performance Improvements:
New Measurement Features:
Configuration:
ILogger Integration:
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<Program>();
var chrono = new ChronolapTimer();
chrono.Start();
Thread.Sleep(500);
chrono.Lap("First operation");
Thread.Sleep(700);
chrono.Lap("Second operation");
chrono.Stop();
logger.LogLaps(chrono, LogLevel.Information);
Output will looks like this;
info: Program[0]
Chronolap Results:
Lap 1: 00:00:00.5000000
Lap 2: 00:00:01.2000000
If you find this project useful, consider supporting me: