diff --git a/docs/Releases.md b/docs/Releases.md
index 5aaac0c1..a1242f94 100644
--- a/docs/Releases.md
+++ b/docs/Releases.md
@@ -16,6 +16,7 @@ This page tracks major changes included in any update starting with version 4.0.
- Added `MiniProfiler.Minimal` headless package with is a standalone bare-bones build with no depdencies and no UI, useful for mass scale applications that are viewing the results elsewhere ([#636](https://github.com/MiniProfiler/dotnet/pull/636))
- Fixed [#578](https://github.com/MiniProfiler/dotnet/issues/578): Making SQLite data types compatible for more use cases ([#582](https://github.com/MiniProfiler/dotnet/pull/582) - thanks [MarkZither](https://github.com/MarkZither))
- Add Nullable Reference Type annotations to the entire codebase ([#640](https://github.com/MiniProfiler/dotnet/pull/640))
+ - Add `MiniProfilerOptions.TimingInstrumentationProvider` allowing to hook when `Timing`s are created, e.g. to drive `Activity` if desired ([#650](https://github.com/MiniProfiler/dotnet/pull/650) - thanks [m0sa](https://github.com/m0sa))
#### Version 4.2.22
- Minor fixes to build versioning
diff --git a/src/MiniProfiler.Shared/Internal/MiniProfilerBaseOptions.cs b/src/MiniProfiler.Shared/Internal/MiniProfilerBaseOptions.cs
index b76e78bd..c798a546 100644
--- a/src/MiniProfiler.Shared/Internal/MiniProfilerBaseOptions.cs
+++ b/src/MiniProfiler.Shared/Internal/MiniProfilerBaseOptions.cs
@@ -221,6 +221,12 @@ public class MiniProfilerBaseOptions
///
public MiniProfiler? StartProfiler(string? profilerName = null) => ProfilerProvider.Start(profilerName, this);
+ ///
+ /// Called whenever a new is started.
+ /// The method of the returned object is called at the same time as the is ed.
+ ///
+ public Func? TimingInstrumentationProvider { get; set; }
+
///
/// Called when passed to .
///
diff --git a/src/MiniProfiler.Shared/Timing.cs b/src/MiniProfiler.Shared/Timing.cs
index 6298553a..9f80d2ec 100644
--- a/src/MiniProfiler.Shared/Timing.cs
+++ b/src/MiniProfiler.Shared/Timing.cs
@@ -20,6 +20,7 @@ public class Timing : IDisposable
private readonly decimal? _minSaveMs;
private readonly bool _includeChildrenWithMinSave;
private readonly object _syncRoot = new();
+ private readonly IDisposable? _instrumentation = null;
///
/// Initializes a new instance of the class.
@@ -77,6 +78,9 @@ public Timing(MiniProfiler profiler, Timing? parent, string? name, decimal? minS
{
DebugInfo = new TimingDebugInfo(this, debugStackShave);
}
+
+ // DataContractSerializer doesn't call this so it should be fine
+ _instrumentation = profiler.Options.TimingInstrumentationProvider?.Invoke(this);
}
///
@@ -282,6 +286,8 @@ public void Stop()
ParentTiming.RemoveChild(this);
}
}
+
+ _instrumentation?.Dispose();
}
///
diff --git a/tests/MiniProfiler.Tests/TimingInstrumentationTest.cs b/tests/MiniProfiler.Tests/TimingInstrumentationTest.cs
new file mode 100644
index 00000000..a8e1a230
--- /dev/null
+++ b/tests/MiniProfiler.Tests/TimingInstrumentationTest.cs
@@ -0,0 +1,38 @@
+using System;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace StackExchange.Profiling.Tests
+{
+ public class TimingInstrumentationTest : BaseTest
+ {
+ public TimingInstrumentationTest(ITestOutputHelper output) : base(output) { }
+
+ private class TimingInstrumentation : IDisposable
+ {
+ public Timing Timing { get; set; }
+ public bool Disposed { get; set; }
+ public void Dispose() => Disposed = true;
+ }
+
+ [Fact]
+ public void IsInstrumented()
+ {
+ TimingInstrumentation instrumentation = null;
+ Timing timing = null;
+ Options.TimingInstrumentationProvider = t => instrumentation = new TimingInstrumentation { Timing = t };
+ var mp = Options.StartProfiler();
+
+ using (timing = mp.Step("Test timing"))
+ {
+ Assert.NotNull(instrumentation);
+ Assert.False(instrumentation.Disposed);
+ mp.Increment();
+ }
+
+ Assert.NotNull(instrumentation);
+ Assert.Equal(timing, instrumentation.Timing);
+ Assert.True(instrumentation.Disposed);
+ }
+ }
+}