Skip to content

Commit 80da32a

Browse files
authored
Collect StackGuard stats (#18941)
1 parent b320870 commit 80da32a

File tree

9 files changed

+149
-53
lines changed

9 files changed

+149
-53
lines changed

src/Compiler/Driver/fsc.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ let main1
569569
exiter.Exit 1
570570

571571
if tcConfig.showTimes then
572+
StackGuardMetrics.CaptureStatsAndWriteToConsole() |> disposables.Register
572573
Caches.CacheMetrics.CaptureStatsAndWriteToConsole() |> disposables.Register
573574
Activity.Profiling.addConsoleListener () |> disposables.Register
574575

src/Compiler/Facilities/DiagnosticsLogger.fs

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ open System.Runtime.InteropServices
1616
open Internal.Utilities.Library
1717
open Internal.Utilities.Library.Extras
1818
open System.Threading.Tasks
19+
open System.Collections.Concurrent
1920

2021
/// Represents the style being used to format errors
2122
[<RequireQualifiedAccess; NoComparison; NoEquality>]
@@ -868,6 +869,66 @@ let internal languageFeatureNotSupportedInLibraryError (langFeature: LanguageFea
868869
let suggestedVersionStr = LanguageVersion.GetFeatureVersionString langFeature
869870
error (Error(FSComp.SR.chkFeatureNotSupportedInLibrary (featureStr, suggestedVersionStr), m))
870871

872+
module StackGuardMetrics =
873+
874+
let meter = FSharp.Compiler.Diagnostics.Metrics.Meter
875+
876+
let jumpCounter =
877+
meter.CreateCounter<int64>(
878+
"stackguard-jumps",
879+
description = "Tracks the number of times the stack guard has jumped to a new thread"
880+
)
881+
882+
let countJump memberName location =
883+
let tags =
884+
let mutable tags = TagList()
885+
tags.Add(Activity.Tags.callerMemberName, memberName)
886+
tags.Add("source", location)
887+
tags
888+
889+
jumpCounter.Add(1L, &tags)
890+
891+
// Used by the self-listener.
892+
let jumpsByFunctionName = ConcurrentDictionary<_, int64 ref>()
893+
894+
let Listen () =
895+
let listener = new Metrics.MeterListener()
896+
897+
listener.EnableMeasurementEvents jumpCounter
898+
899+
listener.SetMeasurementEventCallback(fun _ v tags _ ->
900+
let memberName = nonNull tags[0].Value :?> string
901+
let source = nonNull tags[1].Value :?> string
902+
let counter = jumpsByFunctionName.GetOrAdd((memberName, source), fun _ -> ref 0L)
903+
Interlocked.Add(counter, v) |> ignore)
904+
905+
listener.Start()
906+
listener :> IDisposable
907+
908+
let StatsToString () =
909+
let headers = [ "caller"; "source"; "jumps" ]
910+
911+
let data =
912+
[
913+
for kvp in jumpsByFunctionName do
914+
let (memberName, source) = kvp.Key
915+
[ memberName; source; string kvp.Value.Value ]
916+
]
917+
918+
if List.isEmpty data then
919+
""
920+
else
921+
$"StackGuard jumps:\n{Metrics.printTable headers data}"
922+
923+
let CaptureStatsAndWriteToConsole () =
924+
let listener = Listen()
925+
926+
{ new IDisposable with
927+
member _.Dispose() =
928+
listener.Dispose()
929+
StatsToString() |> printfn "%s"
930+
}
931+
871932
/// Guard against depth of expression nesting, by moving to new stack when a maximum depth is reached
872933
type StackGuard(maxDepth: int, name: string) =
873934

@@ -882,22 +943,15 @@ type StackGuard(maxDepth: int, name: string) =
882943
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int
883944
) =
884945

885-
Activity.addEventWithTags
886-
"DiagnosticsLogger.StackGuard.Guard"
887-
(seq {
888-
Activity.Tags.stackGuardName, box name
889-
Activity.Tags.stackGuardCurrentDepth, depth
890-
Activity.Tags.stackGuardMaxDepth, maxDepth
891-
Activity.Tags.callerMemberName, memberName
892-
Activity.Tags.callerFilePath, path
893-
Activity.Tags.callerLineNumber, line
894-
})
895-
896946
depth <- depth + 1
897947

898948
try
899949
if depth % maxDepth = 0 then
900950

951+
let fileName = System.IO.Path.GetFileName(path)
952+
953+
StackGuardMetrics.countJump memberName $"{fileName}:{line}"
954+
901955
async {
902956
do! Async.SwitchToNewThread()
903957
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depth})"

src/Compiler/Facilities/DiagnosticsLogger.fsi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,11 @@ val tryLanguageFeatureErrorOption:
459459

460460
val languageFeatureNotSupportedInLibraryError: langFeature: LanguageFeature -> m: range -> 'T
461461

462+
module internal StackGuardMetrics =
463+
val Listen: unit -> IDisposable
464+
val StatsToString: unit -> string
465+
val CaptureStatsAndWriteToConsole: unit -> IDisposable
466+
462467
type StackGuard =
463468
new: maxDepth: int * name: string -> StackGuard
464469

src/Compiler/Utilities/Activity.fs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,54 @@ module ActivityNames =
1818

1919
let AllRelevantNames = [| FscSourceName; ProfiledSourceName |]
2020

21+
module Metrics =
22+
let Meter = new Metrics.Meter(ActivityNames.FscSourceName)
23+
24+
let formatTable headers rows =
25+
let columnWidths =
26+
headers :: rows
27+
|> List.transpose
28+
|> List.map (List.map String.length >> List.max)
29+
30+
let center width (cell: string) =
31+
String.replicate ((width - cell.Length) / 2) " " + cell |> _.PadRight(width)
32+
33+
let headers = (columnWidths, headers) ||> List.map2 center
34+
35+
let printRow (row: string list) =
36+
row
37+
|> List.mapi (fun i (cell: string) ->
38+
if i = 0 then
39+
cell.PadRight(columnWidths[i])
40+
else
41+
cell.PadLeft(columnWidths[i]))
42+
|> String.concat " | "
43+
|> sprintf "| %s |"
44+
45+
let headerRow = printRow headers
46+
47+
let divider = headerRow |> String.map (fun c -> if c = '|' then c else '-')
48+
let hl = String.replicate divider.Length "-"
49+
50+
use sw = new StringWriter()
51+
52+
sw.WriteLine hl
53+
sw.WriteLine headerRow
54+
sw.WriteLine divider
55+
56+
for row in rows do
57+
sw.WriteLine(printRow row)
58+
59+
sw.WriteLine hl
60+
61+
string sw
62+
63+
let printTable headers rows =
64+
try
65+
formatTable headers rows
66+
with exn ->
67+
$"Error formatting table: {exn}"
68+
2169
[<RequireQualifiedAccess>]
2270
module internal Activity =
2371

src/Compiler/Utilities/Activity.fsi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
namespace FSharp.Compiler.Diagnostics
33

44
open System
5-
open Internal.Utilities.Library
5+
open System.Diagnostics.Metrics
66

77
/// For activities following the dotnet distributed tracing concept
88
/// https://learn.microsoft.com/dotnet/core/diagnostics/distributed-tracing-concepts?source=recommendations
@@ -16,6 +16,11 @@ module ActivityNames =
1616

1717
val AllRelevantNames: string[]
1818

19+
module internal Metrics =
20+
val Meter: Meter
21+
22+
val printTable: headers: string list -> rows: string list list -> string
23+
1924
/// For activities following the dotnet distributed tracing concept
2025
/// https://learn.microsoft.com/dotnet/core/diagnostics/distributed-tracing-concepts?source=recommendations
2126
[<RequireQualifiedAccess>]

src/Compiler/Utilities/Caches.fs

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ open System.Diagnostics.Metrics
1010
open System.IO
1111

1212
module CacheMetrics =
13-
let Meter = new Meter("FSharp.Compiler.Cache")
13+
let Meter = FSharp.Compiler.Diagnostics.Metrics.Meter
1414
let adds = Meter.CreateCounter<int64>("adds", "count")
1515
let updates = Meter.CreateCounter<int64>("updates", "count")
1616
let hits = Meter.CreateCounter<int64>("hits", "count")
@@ -96,43 +96,24 @@ module CacheMetrics =
9696
listener :> IDisposable
9797

9898
let StatsToString () =
99-
use sw = new StringWriter()
100-
101-
let nameColumnWidth =
102-
[ yield! statsByName.Keys; "Cache name" ] |> Seq.map String.length |> Seq.max
103-
104-
let columns = allCounters |> List.map _.Name
105-
let columnWidths = columns |> List.map String.length |> List.map (max 8)
106-
107-
let header =
108-
"| "
109-
+ String.concat
110-
" | "
111-
[
112-
"Cache name".PadRight nameColumnWidth
113-
"hit-ratio"
114-
for w, c in (columnWidths, columns) ||> List.zip do
115-
$"{c.PadLeft w}"
116-
]
117-
+ " |"
118-
119-
sw.WriteLine(String('-', header.Length))
120-
sw.WriteLine(header)
121-
sw.WriteLine(header |> String.map (fun c -> if c = '|' then '|' else '-'))
122-
123-
for kv in statsByName do
124-
let name = kv.Key
125-
let stats = kv.Value
126-
let totals = stats.GetTotals()
127-
sw.Write $"| {name.PadLeft nameColumnWidth} | {stats.Ratio, 9:P2} |"
128-
129-
for w, c in (columnWidths, columns) ||> List.zip do
130-
sw.Write $" {totals[c].ToString().PadLeft(w)} |"
131-
132-
sw.WriteLine()
133-
134-
sw.WriteLine(String('-', header.Length))
135-
string sw
99+
let headers = [ "Cache name"; "hit-ratio" ] @ (allCounters |> List.map _.Name)
100+
101+
let rows =
102+
[
103+
for kv in statsByName do
104+
let name = kv.Key
105+
let stats = kv.Value
106+
let totals = stats.GetTotals()
107+
108+
[
109+
yield name
110+
yield $"{stats.Ratio:P2}"
111+
for c in allCounters do
112+
yield $"{totals[c.Name]}"
113+
]
114+
]
115+
116+
FSharp.Compiler.Diagnostics.Metrics.printTable headers rows
136117

137118
let CaptureStatsAndWriteToConsole () =
138119
let listener = ListenToAll()

tests/FSharp.Test.Utilities/XunitHelpers.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ type OpenTelemetryExport(testRunName, enable) =
175175

176176
// Configure OpenTelemetry metrics export. Metrics can be viewed in Prometheus or other compatible tools.
177177
OpenTelemetry.Sdk.CreateMeterProviderBuilder()
178-
.AddMeter(CacheMetrics.Meter.Name)
178+
.AddMeter(ActivityNames.FscSourceName)
179179
.AddMeter("System.Runtime")
180180
.ConfigureResource(fun r -> r.AddService(testRunName) |> ignore)
181181
.AddOtlpExporter(fun e m ->

vsintegration/src/FSharp.Editor/Common/DebugHelpers.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,15 @@ module FSharpServiceTelemetry =
120120

121121
ActivitySource.AddActivityListener(listener)
122122

123-
let periodicallyDisplayCacheStats =
123+
let periodicallyDisplayMetrics =
124124
cancellableTask {
125125
use _ = CacheMetrics.ListenToAll()
126+
use _ = FSharp.Compiler.DiagnosticsLogger.StackGuardMetrics.Listen()
126127

127128
while true do
128129
do! Task.Delay(TimeSpan.FromSeconds 10.0)
129130
FSharpOutputPane.logMsg (CacheMetrics.StatsToString())
131+
FSharpOutputPane.logMsg (FSharp.Compiler.DiagnosticsLogger.StackGuardMetrics.StatsToString())
130132
}
131133

132134
#if DEBUG

vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ type internal FSharpPackage() as this =
417417
false,
418418
fun _ _ ->
419419
task {
420-
DebugHelpers.FSharpServiceTelemetry.periodicallyDisplayCacheStats
420+
DebugHelpers.FSharpServiceTelemetry.periodicallyDisplayMetrics
421421
|> CancellableTask.start this.DisposalToken
422422
|> ignore
423423
}

0 commit comments

Comments
 (0)