Skip to content

Commit 1120572

Browse files
authored
Add support for hooking in additional instrumentation around Timings (#650)
Most of the code of _a project that I'm working on_ is instrumented via MiniProfiler's extension methods. In order to get the MiniProfiler instrumented sections/traces into 3rd party APM tools, we'd either need to hijack all the MiniProfiler extension method combinations, or add yet another `using` around everything we instrument via MiniProfiler. The latter is particularly impractical for the `.Inline(() => ....)` extension methods etc.
1 parent 9f689c5 commit 1120572

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

docs/Releases.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This page tracks major changes included in any update starting with version 4.0.
1616
- 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))
1717
- 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))
1818
- Add Nullable Reference Type annotations to the entire codebase ([#640](https://github.com/MiniProfiler/dotnet/pull/640))
19+
- 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))
1920

2021
#### Version 4.2.22
2122
- Minor fixes to build versioning

src/MiniProfiler.Shared/Internal/MiniProfilerBaseOptions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ public class MiniProfilerBaseOptions
221221
/// </param>
222222
public MiniProfiler? StartProfiler(string? profilerName = null) => ProfilerProvider.Start(profilerName, this);
223223

224+
/// <summary>
225+
/// Called whenever a new <cref see="Timing" /> is started.
226+
/// The <cref see="IDiposable.Dispose" /> method of the returned object is called at the same time as the <cref see="Timing" /> is <cref see="Timing.Stop" />ed.
227+
/// </summary>
228+
public Func<Timing, IDisposable>? TimingInstrumentationProvider { get; set; }
229+
224230
/// <summary>
225231
/// Called when passed to <see cref="MiniProfiler.Configure{T}(T)"/>.
226232
/// </summary>

src/MiniProfiler.Shared/Timing.cs

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class Timing : IDisposable
2020
private readonly decimal? _minSaveMs;
2121
private readonly bool _includeChildrenWithMinSave;
2222
private readonly object _syncRoot = new();
23+
private readonly IDisposable? _instrumentation = null;
2324

2425
/// <summary>
2526
/// Initializes a new instance of the <see cref="Timing"/> class.
@@ -77,6 +78,9 @@ public Timing(MiniProfiler profiler, Timing? parent, string? name, decimal? minS
7778
{
7879
DebugInfo = new TimingDebugInfo(this, debugStackShave);
7980
}
81+
82+
// DataContractSerializer doesn't call this so it should be fine
83+
_instrumentation = profiler.Options.TimingInstrumentationProvider?.Invoke(this);
8084
}
8185

8286
/// <summary>
@@ -282,6 +286,8 @@ public void Stop()
282286
ParentTiming.RemoveChild(this);
283287
}
284288
}
289+
290+
_instrumentation?.Dispose();
285291
}
286292

287293
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using Xunit;
3+
using Xunit.Abstractions;
4+
5+
namespace StackExchange.Profiling.Tests
6+
{
7+
public class TimingInstrumentationTest : BaseTest
8+
{
9+
public TimingInstrumentationTest(ITestOutputHelper output) : base(output) { }
10+
11+
private class TimingInstrumentation : IDisposable
12+
{
13+
public Timing Timing { get; set; }
14+
public bool Disposed { get; set; }
15+
public void Dispose() => Disposed = true;
16+
}
17+
18+
[Fact]
19+
public void IsInstrumented()
20+
{
21+
TimingInstrumentation instrumentation = null;
22+
Timing timing = null;
23+
Options.TimingInstrumentationProvider = t => instrumentation = new TimingInstrumentation { Timing = t };
24+
var mp = Options.StartProfiler();
25+
26+
using (timing = mp.Step("Test timing"))
27+
{
28+
Assert.NotNull(instrumentation);
29+
Assert.False(instrumentation.Disposed);
30+
mp.Increment();
31+
}
32+
33+
Assert.NotNull(instrumentation);
34+
Assert.Equal(timing, instrumentation.Timing);
35+
Assert.True(instrumentation.Disposed);
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)