Skip to content

Commit 09680e2

Browse files
committed
IInProcessDiagnoser returns Type and serialized config instead of source code.
1 parent 16b74c4 commit 09680e2

File tree

8 files changed

+170
-103
lines changed

8 files changed

+170
-103
lines changed

src/BenchmarkDotNet/Diagnosers/CompositeDiagnoser.cs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
44
using System.ComponentModel;
5-
using System.Diagnostics;
65
using System.Linq;
76
using BenchmarkDotNet.Analysers;
87
using BenchmarkDotNet.Engines;
98
using BenchmarkDotNet.Exporters;
109
using BenchmarkDotNet.Extensions;
10+
using BenchmarkDotNet.Helpers;
1111
using BenchmarkDotNet.Loggers;
1212
using BenchmarkDotNet.Reports;
1313
using BenchmarkDotNet.Running;
@@ -64,16 +64,39 @@ public sealed class CompositeInProcessDiagnoser(IReadOnlyList<IInProcessDiagnose
6464
public const string ResultsKey = $"{HeaderKey}Results";
6565

6666
public IEnumerable<string> GetSourceCode(BenchmarkCase benchmarkCase)
67-
=> GetInProcessDiagnoserRouters(benchmarkCase)
68-
.Select(router => router.ToSourceCode());
67+
=> inProcessDiagnosers
68+
.Select((d, i) => ToSourceCode(d, benchmarkCase, i))
69+
.WhereNotNull();
6970

7071
public IEnumerable<InProcessDiagnoserRouter> GetInProcessDiagnoserRouters(BenchmarkCase benchmarkCase)
7172
=> inProcessDiagnosers
72-
.Select((d, i) => new InProcessDiagnoserRouter() { index = i, runMode = d.GetRunMode(benchmarkCase), handler = d.GetHandler(benchmarkCase) })
73-
.Where(router => router.handler != null);
73+
.Select((d, i) => new InProcessDiagnoserRouter()
74+
{
75+
index = i,
76+
runMode = d.GetRunMode(benchmarkCase),
77+
handler = d.GetSameProcessHandler(benchmarkCase)
78+
})
79+
.Where(r => r.handler != null);
7480

7581
public void DeserializeResults(int index, BenchmarkCase benchmarkCase, string results)
7682
=> inProcessDiagnosers[index].DeserializeResults(benchmarkCase, results);
83+
84+
private static string? ToSourceCode(IInProcessDiagnoser diagnoser, BenchmarkCase benchmarkCase, int index)
85+
{
86+
var (handlerType, serializedConfig) = diagnoser.GetSeparateProcessHandlerTypeAndSerializedConfig(benchmarkCase);
87+
if (handlerType is null)
88+
{
89+
return null;
90+
}
91+
string routerType = typeof(InProcessDiagnoserRouter).GetCorrectCSharpTypeName();
92+
return $$"""
93+
new {{routerType}}() {
94+
{{nameof(InProcessDiagnoserRouter.handler)}} = {{routerType}}.{{nameof(InProcessDiagnoserRouter.Init)}}(new {{handlerType.GetCorrectCSharpTypeName()}}(), {{SourceCodeHelper.ToSourceCode(serializedConfig)}}),
95+
{{nameof(InProcessDiagnoserRouter.index)}} = {{index}},
96+
{{nameof(InProcessDiagnoserRouter.runMode)}} = {{SourceCodeHelper.ToSourceCode(diagnoser.GetRunMode(benchmarkCase))}}
97+
}
98+
""";
99+
}
77100
}
78101

79102
[UsedImplicitly]

src/BenchmarkDotNet/Diagnosers/IDiagnoser.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
24
using BenchmarkDotNet.Analysers;
35
using BenchmarkDotNet.Engines;
46
using BenchmarkDotNet.Exporters;
@@ -40,24 +42,41 @@ public interface IConfigurableDiagnoser<in TConfig> : IDiagnoser
4042
public interface IInProcessDiagnoser : IDiagnoser
4143
{
4244
/// <summary>
43-
/// Gets the diagnoser handler.
45+
/// Gets the type of the handler that will run in the separate benchmark process and its serialized config.
46+
/// </summary>
47+
/// <remarks>
48+
/// The handlerType must implement <see cref="IInProcessDiagnoserHandler"/> and have a publicly accessible default constructor.
49+
/// <para/>
50+
/// Return <see langword="default"/> to not run the diagnoser handler for the <paramref name="benchmarkCase"/>.
51+
/// </remarks>
52+
(Type? handlerType, string? serializedConfig) GetSeparateProcessHandlerTypeAndSerializedConfig(BenchmarkCase benchmarkCase);
53+
54+
// GetSameProcessHandler is needed to prevent the handler type from being trimmed for InProcess toolchains.
55+
56+
/// <summary>
57+
/// Gets the handler that will run in the same process.
4458
/// </summary>
4559
/// <remarks>
4660
/// Return <see langword="null"/> to not run the diagnoser handler for the <paramref name="benchmarkCase"/>.
4761
/// </remarks>
48-
IInProcessDiagnoserHandler? GetHandler(BenchmarkCase benchmarkCase);
62+
IInProcessDiagnoserHandler? GetSameProcessHandler(BenchmarkCase benchmarkCase);
4963

5064
/// <summary>
5165
/// Deserializes the results of the handler.
5266
/// </summary>
53-
void DeserializeResults(BenchmarkCase benchmarkCase, string results);
67+
void DeserializeResults(BenchmarkCase benchmarkCase, string serializedResults);
5468
}
5569

5670
/// <summary>
5771
/// Represents a handler for an <see cref="IInProcessDiagnoser"/>.
5872
/// </summary>
5973
public interface IInProcessDiagnoserHandler
6074
{
75+
/// <summary>
76+
/// Initializes the handler with the serialized config from the host <see cref="IInProcessDiagnoser"/>.
77+
/// </summary>
78+
void Initialize(string? serializedConfig);
79+
6180
/// <summary>
6281
/// Handles the signal from the benchmark.
6382
/// </summary>
@@ -67,13 +86,5 @@ public interface IInProcessDiagnoserHandler
6786
/// Serializes the results to be sent back to the host <see cref="IInProcessDiagnoser"/>.
6887
/// </summary>
6988
string SerializeResults();
70-
71-
/// <summary>
72-
/// Gets the C# source code used to instantiate the handler in the benchmark process.
73-
/// </summary>
74-
/// <remarks>
75-
/// The source code must be a single expression.
76-
/// </remarks>
77-
string ToSourceCode();
7889
}
7990
}
Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using BenchmarkDotNet.Extensions;
2-
using BenchmarkDotNet.Helpers;
3-
using JetBrains.Annotations;
1+
using JetBrains.Annotations;
42
using System.ComponentModel;
53

64
namespace BenchmarkDotNet.Diagnosers;
@@ -13,12 +11,9 @@ public struct InProcessDiagnoserRouter
1311
public int index;
1412
public RunMode runMode;
1513

16-
public readonly string ToSourceCode()
17-
=> $$"""
18-
new {{typeof(InProcessDiagnoserRouter).GetCorrectCSharpTypeName()}}() {
19-
{{nameof(handler)}} = {{handler.ToSourceCode()}},
20-
{{nameof(index)}} = {{index}},
21-
{{nameof(runMode)}} = {{SourceCodeHelper.ToSourceCode(runMode)}}
22-
}
23-
""";
14+
public static IInProcessDiagnoserHandler Init(IInProcessDiagnoserHandler handler, string serializedConfig)
15+
{
16+
handler.Initialize(serializedConfig);
17+
return handler;
18+
}
2419
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Linq;
3+
using SimpleJson;
4+
5+
namespace BenchmarkDotNet.Disassemblers
6+
{
7+
internal struct ClrMdArgs(int processId, string typeName, string methodName, bool printSource, int maxDepth, string syntax, string tfm, string[] filters, string resultsPath = null)
8+
{
9+
internal int ProcessId = processId;
10+
internal string TypeName = typeName;
11+
internal string MethodName = methodName;
12+
internal bool PrintSource = printSource;
13+
internal int MaxDepth = methodName == DisassemblerConstants.DisassemblerEntryMethodName && maxDepth != int.MaxValue ? maxDepth + 1 : maxDepth;
14+
internal string[] Filters = filters;
15+
internal string Syntax = syntax;
16+
internal string TargetFrameworkMoniker = tfm;
17+
internal string ResultsPath = resultsPath;
18+
19+
internal static ClrMdArgs FromArgs(string[] args)
20+
=> new(
21+
processId: int.Parse(args[0]),
22+
typeName: args[1],
23+
methodName: args[2],
24+
printSource: bool.Parse(args[3]),
25+
maxDepth: int.Parse(args[4]),
26+
resultsPath: args[5],
27+
syntax: args[6],
28+
tfm: args[7],
29+
filters: [.. args.Skip(8)]
30+
);
31+
32+
internal readonly string Serialize()
33+
{
34+
SimpleJsonSerializer.CurrentJsonSerializerStrategy.Indent = false;
35+
var jsonObject = new JsonObject()
36+
{
37+
[nameof(MethodName)] = MethodName,
38+
[nameof(PrintSource)] = PrintSource,
39+
[nameof(MaxDepth)] = MaxDepth,
40+
[nameof(Syntax)] = Syntax,
41+
[nameof(TargetFrameworkMoniker)] = TargetFrameworkMoniker,
42+
[nameof(ResultsPath)] = ResultsPath,
43+
};
44+
var filters = new JsonArray(Filters.Length);
45+
foreach (var filter in Filters)
46+
{
47+
filters.Add(filter);
48+
}
49+
jsonObject[nameof(Filters)] = filters;
50+
return jsonObject.ToString();
51+
}
52+
53+
internal void Deserialize(string json)
54+
{
55+
var jsonObject = SimpleJsonSerializer.DeserializeObject<JsonObject>(json);
56+
MethodName = (string) jsonObject[nameof(MethodName)];
57+
PrintSource = (bool) jsonObject[nameof(PrintSource)];
58+
MaxDepth = Convert.ToInt32(jsonObject[nameof(MaxDepth)]);
59+
Syntax = (string) jsonObject[nameof(Syntax)];
60+
TargetFrameworkMoniker = (string) jsonObject[nameof(TargetFrameworkMoniker)];
61+
ResultsPath = (string) jsonObject[nameof(ResultsPath)];
62+
var filters = (JsonArray) jsonObject[nameof(Filters)];
63+
Filters = new string[filters.Count];
64+
for (int i = 0; i < filters.Count; ++i)
65+
{
66+
Filters[i] = (string) filters[i];
67+
}
68+
}
69+
}
70+
}

src/BenchmarkDotNet/Disassemblers/ClrMdDisassembler.cs

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,10 @@
88
using System.Text.RegularExpressions;
99
using BenchmarkDotNet.Detectors;
1010
using BenchmarkDotNet.Portability;
11-
using JetBrains.Annotations;
12-
using System.ComponentModel;
1311
using Microsoft.Diagnostics.NETCore.Client;
1412

1513
namespace BenchmarkDotNet.Disassemblers
1614
{
17-
[UsedImplicitly]
18-
[EditorBrowsable(EditorBrowsableState.Never)]
19-
public sealed class ClrMdArgs(int processId, string typeName, string methodName, bool printSource, int maxDepth, string syntax, string tfm, string[] filters, string resultsPath = null)
20-
{
21-
internal int ProcessId { get; } = processId;
22-
internal string TypeName { get; } = typeName;
23-
internal string MethodName { get; } = methodName;
24-
internal bool PrintSource { get; } = printSource;
25-
internal int MaxDepth { get; } = methodName == DisassemblerConstants.DisassemblerEntryMethodName && maxDepth != int.MaxValue ? maxDepth + 1 : maxDepth;
26-
internal string[] Filters { get; } = filters;
27-
internal string Syntax { get; } = syntax;
28-
internal string TargetFrameworkMoniker { get; } = tfm;
29-
internal string ResultsPath { get; } = resultsPath;
30-
31-
internal static ClrMdArgs FromArgs(string[] args)
32-
=> new(
33-
processId: int.Parse(args[0]),
34-
typeName: args[1],
35-
methodName: args[2],
36-
printSource: bool.Parse(args[3]),
37-
maxDepth: int.Parse(args[4]),
38-
resultsPath: args[5],
39-
syntax: args[6],
40-
tfm: args[7],
41-
filters: [.. args.Skip(8)]
42-
);
43-
}
44-
4515
internal abstract class ClrMdDisassembler
4616

4717
{
@@ -113,32 +83,32 @@ private DataTarget Attach(int processId)
11383
throw new NotSupportedException($"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} is not supported");
11484
}
11585

116-
internal DisassemblyResult AttachAndDisassemble(ClrMdArgs settings)
86+
internal DisassemblyResult AttachAndDisassemble(ClrMdArgs args)
11787
{
118-
using var dataTarget = Attach(settings.ProcessId);
88+
using var dataTarget = Attach(args.ProcessId);
11989

12090
var runtime = dataTarget.ClrVersions.Single().CreateRuntime();
12191

12292
ConfigureSymbols(dataTarget);
12393

124-
var state = new State(runtime, settings.TargetFrameworkMoniker);
94+
var state = new State(runtime, args.TargetFrameworkMoniker);
12595

126-
if (settings.Filters.Length > 0)
96+
if (args.Filters.Length > 0)
12797
{
128-
FilterAndEnqueue(state, settings);
98+
FilterAndEnqueue(state, args);
12999
}
130100
else
131101
{
132-
ClrType typeWithBenchmark = state.Runtime.EnumerateModules().Select(module => module.GetTypeByName(settings.TypeName)).First(type => type != null);
102+
ClrType typeWithBenchmark = state.Runtime.EnumerateModules().Select(module => module.GetTypeByName(args.TypeName)).First(type => type != null);
133103

134104
state.Todo.Enqueue(
135105
new MethodInfo(
136106
// the Disassembler Entry Method is always parameterless, so check by name is enough
137-
typeWithBenchmark.Methods.Single(method => method.Attributes.HasFlag(System.Reflection.MethodAttributes.Public) && method.Name == settings.MethodName),
107+
typeWithBenchmark.Methods.Single(method => method.Attributes.HasFlag(System.Reflection.MethodAttributes.Public) && method.Name == args.MethodName),
138108
0));
139109
}
140110

141-
var disassembledMethods = Disassemble(settings, state);
111+
var disassembledMethods = Disassemble(args, state);
142112

143113
// we don't want to export the disassembler entry point method which is just an artificial method added to get generic types working
144114
var filteredMethods = disassembledMethods.Length == 1

0 commit comments

Comments
 (0)