8
8
using BenchmarkDotNet . Exporters ;
9
9
using BenchmarkDotNet . Extensions ;
10
10
using BenchmarkDotNet . Helpers ;
11
+ using BenchmarkDotNet . Jobs ;
11
12
using BenchmarkDotNet . Loggers ;
12
13
using BenchmarkDotNet . Portability ;
13
14
using BenchmarkDotNet . Reports ;
14
15
using BenchmarkDotNet . Running ;
16
+ using BenchmarkDotNet . Toolchains ;
17
+ using BenchmarkDotNet . Toolchains . CoreRun ;
18
+ using BenchmarkDotNet . Toolchains . CsProj ;
19
+ using BenchmarkDotNet . Toolchains . DotNetCli ;
20
+ using BenchmarkDotNet . Toolchains . NativeAot ;
15
21
using BenchmarkDotNet . Validators ;
16
22
using JetBrains . Annotations ;
17
23
using Mono . Unix . Native ;
@@ -28,6 +34,7 @@ public class PerfCollectProfiler : IProfiler
28
34
private readonly PerfCollectProfilerConfig config ;
29
35
private readonly DateTime creationTime = DateTime . Now ;
30
36
private readonly Dictionary < BenchmarkCase , FileInfo > benchmarkToTraceFile = new ( ) ;
37
+ private readonly HashSet < string > cliPathWithSymbolsInstalled = new ( ) ;
31
38
private FileInfo perfCollectFile ;
32
39
private Process perfCollectProcess ;
33
40
@@ -101,7 +108,7 @@ private bool TryInstallPerfCollect(ValidationParameters validationParameters)
101
108
102
109
if ( Syscall . chmod ( perfCollectFile . FullName , FilePermissions . S_IXUSR ) != SuccessExitCode )
103
110
{
104
- logger . WriteError ( $ "Unable to make perfcollect script an executable, the last error was: { Mono . Unix . Native . Syscall . GetLastError ( ) } ") ;
111
+ logger . WriteError ( $ "Unable to make perfcollect script an executable, the last error was: { Syscall . GetLastError ( ) } ") ;
105
112
}
106
113
else
107
114
{
@@ -130,6 +137,8 @@ private bool TryInstallPerfCollect(ValidationParameters validationParameters)
130
137
131
138
private Process StartCollection ( DiagnoserActionParameters parameters )
132
139
{
140
+ EnsureDotnetSymbolIsInstalled ( parameters ) ;
141
+
133
142
var traceName = new FileInfo ( ArtifactFileNameHelper . GetTraceFilePath ( parameters , creationTime , fileExtension : null ) ) . Name ;
134
143
135
144
var start = new ProcessStartInfo
@@ -184,5 +193,82 @@ private void StopCollection(DiagnoserActionParameters parameters)
184
193
perfCollectProcess . Dispose ( ) ;
185
194
}
186
195
}
196
+
197
+ private void EnsureDotnetSymbolIsInstalled ( DiagnoserActionParameters parameters )
198
+ {
199
+ string cliPath = parameters . BenchmarkCase . GetToolchain ( ) switch
200
+ {
201
+ CsProjCoreToolchain core => core . CustomDotNetCliPath ,
202
+ CoreRunToolchain coreRun => coreRun . CustomDotNetCliPath . FullName ,
203
+ NativeAotToolchain nativeAot => nativeAot . CustomDotNetCliPath ,
204
+ _ => null // custom toolchain, dotnet from $PATH will be used
205
+ } ;
206
+
207
+ if ( cliPathWithSymbolsInstalled . Contains ( cliPath ) )
208
+ {
209
+ return ;
210
+ }
211
+
212
+ cliPathWithSymbolsInstalled . Add ( cliPath ) ;
213
+
214
+ ILogger logger = parameters . Config . GetCompositeLogger ( ) ;
215
+ DotNetCliCommand cliCommand = new (
216
+ cliPath : cliPath ,
217
+ arguments : "--info" ,
218
+ generateResult : null ,
219
+ logger : logger ,
220
+ buildPartition : null ,
221
+ environmentVariables : Array . Empty < EnvironmentVariable > ( ) ,
222
+ timeout : TimeSpan . FromMinutes ( 3 ) ,
223
+ logOutput : false ) ;
224
+
225
+ var dotnetInfoResult = DotNetCliCommandExecutor . Execute ( cliCommand ) ;
226
+ if ( ! dotnetInfoResult . IsSuccess )
227
+ {
228
+ logger . WriteError ( $ "Unable to run `dotnet --info` for `{ cliPath } `, dotnet symbol won't be installed") ;
229
+ return ;
230
+ }
231
+
232
+ // sth like "Microsoft.NETCore.App 7.0.0-rc.2.22451.11 [/home/adam/projects/performance/tools/dotnet/x64/shared/Microsoft.NETCore.App]"
233
+ // or "Microsoft.NETCore.App 7.0.0-rc.1.22423.16 [/usr/share/dotnet/shared/Microsoft.NETCore.App]"
234
+ string netCoreAppPath = dotnetInfoResult
235
+ . StandardOutput . Split ( new [ ] { Environment . NewLine } , StringSplitOptions . RemoveEmptyEntries )
236
+ . Where ( line => line . EndsWith ( "Microsoft.NETCore.App]" ) )
237
+ . Select ( line => line . Split ( '[' ) [ 1 ] )
238
+ . Distinct ( )
239
+ . Single ( ) ; // I assume there will be only one such folder
240
+ netCoreAppPath = netCoreAppPath . Substring ( 0 , netCoreAppPath . Length - 1 ) ; // remove trailing `]`
241
+
242
+ string [ ] missingSymbols = Directory . GetFiles ( netCoreAppPath , "lib*.so" , SearchOption . AllDirectories )
243
+ . Where ( nativeLibPath => ! File . Exists ( Path . ChangeExtension ( nativeLibPath , "so.dbg" ) ) )
244
+ . Select ( Path . GetDirectoryName )
245
+ . Distinct ( )
246
+ . ToArray ( ) ;
247
+
248
+ if ( ! missingSymbols . Any ( ) )
249
+ {
250
+ return ; // the symbol files are already where we need them!
251
+ }
252
+
253
+ cliCommand = cliCommand . WithLogOutput ( true ) ; // the following commands might take a while and fail, let's log them
254
+
255
+ // We install the tool in a dedicated directory in order to always use latest version and avoid issues with broken existing configs.
256
+ string toolPath = Path . Combine ( Path . GetTempPath ( ) , "BenchmarkDotNet" , "symbols" ) ;
257
+ var installResult = DotNetCliCommandExecutor . Execute ( cliCommand . WithArguments ( $ "tool install dotnet-symbol --tool-path \" { toolPath } \" ") ) ;
258
+ if ( ! installResult . IsSuccess )
259
+ {
260
+ logger . WriteError ( $ "Unable to install dotnet symbol.") ;
261
+ return ;
262
+ }
263
+
264
+ foreach ( var directoryPath in missingSymbols )
265
+ {
266
+ DotNetCliCommandExecutor . Execute ( cliCommand
267
+ . WithCliPath ( Path . Combine ( toolPath , "dotnet-symbol" ) )
268
+ . WithArguments ( $ "--symbols --output { directoryPath } { directoryPath } /lib*.so") ) ;
269
+ }
270
+
271
+ DotNetCliCommandExecutor . Execute ( cliCommand . WithArguments ( $ "tool uninstall dotnet-symbol --tool-path \" { toolPath } \" ") ) ;
272
+ }
187
273
}
188
274
}
0 commit comments