Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Compiler/Driver/fsc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ let main1
exiter.Exit 1

if tcConfig.showTimes then
StackGuardMetrics.CaptureStatsAndWriteToConsole() |> disposables.Register
Caches.CacheMetrics.CaptureStatsAndWriteToConsole() |> disposables.Register
Activity.Profiling.addConsoleListener () |> disposables.Register

Expand Down
76 changes: 58 additions & 18 deletions src/Compiler/Facilities/DiagnosticsLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ open System.Runtime.InteropServices
open Internal.Utilities.Library
open Internal.Utilities.Library.Extras
open System.Threading.Tasks
open System.Collections.Concurrent

/// Represents the style being used to format errors
[<RequireQualifiedAccess; NoComparison; NoEquality>]
Expand Down Expand Up @@ -868,36 +869,75 @@ let internal languageFeatureNotSupportedInLibraryError (langFeature: LanguageFea
let suggestedVersionStr = LanguageVersion.GetFeatureVersionString langFeature
error (Error(FSComp.SR.chkFeatureNotSupportedInLibrary (featureStr, suggestedVersionStr), m))

module StackGuardMetrics =

let meter = FSharp.Compiler.Diagnostics.Metrics.Meter

let jumpCounter =
meter.CreateCounter<int64>(
"stackguard-jumps",
description = "Tracks the number of times the stack guard has jumped to a new thread"
)

let countJump memberName =
let tags =
let mutable tags = TagList()
tags.Add(Activity.Tags.callerMemberName, memberName)
tags

jumpCounter.Add(1L, &tags)

// Used by the self-listener.
let jumpsByFunctionName = ConcurrentDictionary<string, int64 ref>()

let Listen () =
let listener = new Metrics.MeterListener()

listener.EnableMeasurementEvents jumpCounter

listener.SetMeasurementEventCallback(fun _ v tags _ ->
let memberName = nonNull tags[0].Value :?> string
let counter = jumpsByFunctionName.GetOrAdd(memberName, fun _ -> ref 0L)
Interlocked.Add(counter, v) |> ignore)

listener.Start()
listener :> IDisposable

let StatsToString () =
let entries =
jumpsByFunctionName
|> Seq.map (fun kvp -> $"{kvp.Key}: {kvp.Value.Value}")
|> String.concat ", "

if entries.Length > 0 then
$"StackGuard jumps: {entries} \n"
else
""

let CaptureStatsAndWriteToConsole () =
let listener = Listen()

{ new IDisposable with
member _.Dispose() =
listener.Dispose()
StatsToString() |> printfn "%s"
}

/// Guard against depth of expression nesting, by moving to new stack when a maximum depth is reached
type StackGuard(maxDepth: int, name: string) =

let mutable depth = 1

[<DebuggerHidden; DebuggerStepThrough>]
member _.Guard
(
f,
[<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string,
[<CallerFilePath; Optional; DefaultParameterValue("")>] path: string,
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int
) =

Activity.addEventWithTags
"DiagnosticsLogger.StackGuard.Guard"
(seq {
Activity.Tags.stackGuardName, box name
Activity.Tags.stackGuardCurrentDepth, depth
Activity.Tags.stackGuardMaxDepth, maxDepth
Activity.Tags.callerMemberName, memberName
Activity.Tags.callerFilePath, path
Activity.Tags.callerLineNumber, line
})
member _.Guard(f, [<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string) =

depth <- depth + 1

try
if depth % maxDepth = 0 then

StackGuardMetrics.countJump memberName

async {
do! Async.SwitchToNewThread()
Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depth})"
Expand Down
12 changes: 6 additions & 6 deletions src/Compiler/Facilities/DiagnosticsLogger.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -459,16 +459,16 @@ val tryLanguageFeatureErrorOption:

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

module internal StackGuardMetrics =
val Listen: unit -> IDisposable
val StatsToString: unit -> string
val CaptureStatsAndWriteToConsole: unit -> IDisposable

type StackGuard =
new: maxDepth: int * name: string -> StackGuard

/// Execute the new function, on a new thread if necessary
member Guard:
f: (unit -> 'T) *
[<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string *
[<CallerFilePath; Optional; DefaultParameterValue("")>] path: string *
[<CallerLineNumber; Optional; DefaultParameterValue(0)>] line: int ->
'T
member Guard: f: (unit -> 'T) * [<CallerMemberName; Optional; DefaultParameterValue("")>] memberName: string -> 'T

member GuardCancellable: Internal.Utilities.Library.Cancellable<'T> -> Internal.Utilities.Library.Cancellable<'T>

Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Utilities/Activity.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module ActivityNames =

let AllRelevantNames = [| FscSourceName; ProfiledSourceName |]

module Metrics =
let Meter = new Metrics.Meter(ActivityNames.FscSourceName, "1.0.0")

[<RequireQualifiedAccess>]
module internal Activity =

Expand Down
5 changes: 4 additions & 1 deletion src/Compiler/Utilities/Activity.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
namespace FSharp.Compiler.Diagnostics

open System
open Internal.Utilities.Library
open System.Diagnostics.Metrics

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

val AllRelevantNames: string[]

module internal Metrics =
val Meter: Meter

/// For activities following the dotnet distributed tracing concept
/// https://learn.microsoft.com/dotnet/core/diagnostics/distributed-tracing-concepts?source=recommendations
[<RequireQualifiedAccess>]
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Utilities/Caches.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ open System.Diagnostics.Metrics
open System.IO

module CacheMetrics =
let Meter = new Meter("FSharp.Compiler.Cache")
let Meter = FSharp.Compiler.Diagnostics.Metrics.Meter
let adds = Meter.CreateCounter<int64>("adds", "count")
let updates = Meter.CreateCounter<int64>("updates", "count")
let hits = Meter.CreateCounter<int64>("hits", "count")
Expand Down
2 changes: 1 addition & 1 deletion tests/FSharp.Test.Utilities/XunitHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ type OpenTelemetryExport(testRunName, enable) =

// Configure OpenTelemetry metrics export. Metrics can be viewed in Prometheus or other compatible tools.
OpenTelemetry.Sdk.CreateMeterProviderBuilder()
.AddMeter(CacheMetrics.Meter.Name)
.AddMeter(ActivityNames.FscSourceName)
.AddMeter("System.Runtime")
.ConfigureResource(fun r -> r.AddService(testRunName) |> ignore)
.AddOtlpExporter(fun e m ->
Expand Down
2 changes: 2 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/DebugHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,12 @@ module FSharpServiceTelemetry =
let periodicallyDisplayCacheStats =
cancellableTask {
use _ = CacheMetrics.ListenToAll()
use _ = FSharp.Compiler.DiagnosticsLogger.StackGuardMetrics.Listen()

while true do
do! Task.Delay(TimeSpan.FromSeconds 10.0)
FSharpOutputPane.logMsg (CacheMetrics.StatsToString())
FSharpOutputPane.logMsg (FSharp.Compiler.DiagnosticsLogger.StackGuardMetrics.StatsToString())
}

#if DEBUG
Expand Down
Loading