Skip to content

Commit 794d9da

Browse files
CopilotT-Gro
andcommitted
Add F# compiler timing data extraction for regression tests
- Modified PrepareRepoForRegressionTesting.fsx to inject --times flag - Created ExtractTimingsFromBinlog.fsx to parse binlogs and extract timing data - Updated regression-test-jobs.yml to inject -bl and extract timings Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com>
1 parent ea833f8 commit 794d9da

File tree

3 files changed

+133
-4
lines changed

3 files changed

+133
-4
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/// Script to extract F# compiler timing data from MSBuild binary logs
2+
/// Usage: dotnet fsi ExtractTimingsFromBinlog.fsx <path-to-binlog-file>
3+
4+
#r "nuget: MSBuild.StructuredLogger"
5+
6+
open System
7+
open Microsoft.Build.Logging.StructuredLogger
8+
9+
// Get the binlog file path from command line args
10+
let binlogPath =
11+
let args = Environment.GetCommandLineArgs()
12+
// When running with dotnet fsi, args are: [0]=dotnet; [1]=fsi.dll; [2]=script.fsx; [3...]=args
13+
let scriptArgs = args |> Array.skipWhile (fun a -> not (a.EndsWith(".fsx"))) |> Array.skip 1
14+
if scriptArgs.Length > 0 then
15+
scriptArgs.[0]
16+
else
17+
failwith "Usage: dotnet fsi ExtractTimingsFromBinlog.fsx <path-to-binlog-file>"
18+
19+
// Helper function to find the project node by walking up the parent chain
20+
let rec findProject (node: TreeNode) =
21+
match node with
22+
| null -> None
23+
| :? Project as p -> Some p.Name
24+
| _ -> findProject node.Parent
25+
26+
// Load the binlog
27+
let build = BinaryLog.ReadBuild(binlogPath)
28+
29+
// Track whether we found any Fsc tasks
30+
let mutable foundFscTasks = false
31+
32+
// Walk the build tree looking for Fsc tasks
33+
build.VisitAllChildren<Task>(fun task ->
34+
if task.Name = "Fsc" then
35+
foundFscTasks <- true
36+
37+
// Get the project name
38+
let projectName =
39+
match findProject task with
40+
| Some name -> name
41+
| None -> "Unknown Project"
42+
43+
// Collect all Message children
44+
let messages =
45+
task.Children
46+
|> Seq.choose (function
47+
| :? Message as m -> Some m.Text
48+
| _ -> None)
49+
|> Seq.toList
50+
51+
// Print the project header and messages
52+
if messages.Length > 0 then
53+
printfn "=== %s ===" projectName
54+
for msg in messages do
55+
printfn " %s" msg
56+
printfn ""
57+
)
58+
59+
// If no Fsc tasks were found, print a message
60+
if not foundFscTasks then
61+
printfn "No Fsc task output found in binlog."
62+
63+
// Always exit 0 (this is informational)
64+
exit 0

eng/scripts/PrepareRepoForRegressionTesting.fsx

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ if File.Exists(propsFilePath) then
4848
if isNull projectElement then
4949
failwith "Could not find Project element in Directory.Build.props"
5050

51-
// Check if our import already exists
52-
let xpath = sprintf "//Import[contains(@Project, 'UseLocalCompiler.Directory.Build.props')]"
51+
// Check if our import already exists (look for any import with this exact path)
52+
let xpath = sprintf "//Import[@Project='%s']" absolutePropsPath
5353
let existingImport = doc.SelectSingleNode(xpath)
5454

5555
if isNull existingImport then
@@ -71,11 +71,46 @@ if File.Exists(propsFilePath) then
7171
printfn "✓ Added UseLocalCompiler import to Directory.Build.props"
7272
else
7373
printfn "✓ UseLocalCompiler import already exists"
74+
75+
// Check if --times flag already exists in any OtherFlags element
76+
let otherFlagsWithTimes = doc.SelectSingleNode("//OtherFlags[contains(text(), '--times')]")
77+
78+
if isNull otherFlagsWithTimes then
79+
// Create PropertyGroup with OtherFlags element
80+
let propertyGroup = doc.CreateElement("PropertyGroup")
81+
let otherFlags = doc.CreateElement("OtherFlags")
82+
otherFlags.InnerText <- "$(OtherFlags) --times"
83+
propertyGroup.AppendChild(otherFlags) |> ignore
84+
85+
// Find the import element (either just created or existing)
86+
let importNode = doc.SelectSingleNode("//Import[contains(@Project, 'UseLocalCompiler.Directory.Build.props')]")
87+
88+
// Find the text node after the import (if it exists)
89+
let nodeAfterImport =
90+
if not (isNull importNode) && not (isNull importNode.NextSibling) && importNode.NextSibling.NodeType = XmlNodeType.Text then
91+
importNode.NextSibling
92+
else
93+
null
94+
95+
// Insert PropertyGroup after the import's trailing newline (if present) or after the import itself
96+
if not (isNull nodeAfterImport) then
97+
projectElement.InsertAfter(propertyGroup, nodeAfterImport) |> ignore
98+
else
99+
projectElement.InsertAfter(propertyGroup, importNode) |> ignore
100+
101+
// Add newline for formatting after PropertyGroup
102+
let newlineAfter = doc.CreateTextNode("\n ")
103+
projectElement.InsertAfter(newlineAfter, propertyGroup) |> ignore
104+
105+
doc.Save(propsFilePath)
106+
printfn "✓ Added --times flag to OtherFlags"
107+
else
108+
printfn "✓ --times flag already exists in OtherFlags"
74109
else
75110
printfn "Directory.Build.props does not exist, creating it..."
76-
let newContent = sprintf "<Project>\n <Import Project=\"%s\" />\n</Project>\n" absolutePropsPath
111+
let newContent = sprintf "<Project>\n <Import Project=\"%s\" />\n <PropertyGroup>\n <OtherFlags>$(OtherFlags) --times</OtherFlags>\n </PropertyGroup>\n</Project>\n" absolutePropsPath
77112
File.WriteAllText(propsFilePath, newContent)
78-
printfn "✓ Created Directory.Build.props with UseLocalCompiler import"
113+
printfn "✓ Created Directory.Build.props with UseLocalCompiler import and --times flag"
79114

80115
// Print the final content
81116
printfn ""

eng/templates/regression-test-jobs.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ jobs:
198198
199199
function Run-Command {
200200
param([string]$cmd)
201+
# Ensure binlog is produced for dotnet build/test/msbuild commands
202+
if ($cmd -match '^dotnet\s+(build|test|msbuild)') {
203+
$cmd = "$cmd -bl"
204+
}
201205
if ($cmd -like "dotnet*") {
202206
Write-Host "Executing built-in command: $cmd"
203207
if ($IsWindows) {
@@ -272,6 +276,32 @@ jobs:
272276
condition: always()
273277
continueOnError: true
274278
279+
- pwsh: |
280+
$binlogDir = "$(Pipeline.Workspace)/BinaryLogs"
281+
$script = "$(Build.SourcesDirectory)/eng/scripts/ExtractTimingsFromBinlog.fsx"
282+
283+
if (-not (Test-Path $binlogDir)) {
284+
Write-Host "No binary logs directory found, skipping timing extraction"
285+
exit 0
286+
}
287+
288+
$binlogs = Get-ChildItem -Path $binlogDir -Filter "*.binlog" -ErrorAction SilentlyContinue
289+
if ($binlogs.Count -eq 0) {
290+
Write-Host "No binlog files found, skipping timing extraction"
291+
exit 0
292+
}
293+
294+
Write-Host "##[group]F# Compiler Timing Summary for ${{ item.displayName }}"
295+
foreach ($bl in $binlogs) {
296+
Write-Host ""
297+
Write-Host "--- Timings from $($bl.Name) ---"
298+
dotnet fsi $script "$($bl.FullName)"
299+
}
300+
Write-Host "##[endgroup]"
301+
displayName: Extract compiler timing from binlogs for ${{ item.displayName }}
302+
condition: always()
303+
continueOnError: true
304+
275305
- task: PublishPipelineArtifact@1
276306
displayName: Publish ${{ item.displayName }} Binary Logs
277307
inputs:

0 commit comments

Comments
 (0)