Skip to content

Added fault reporting in telemetry + added opt out for all events #15778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ open FSharp.Compiler.EditorServices
open FSharp.Compiler.Text
open FSharp.Compiler.Text.Range
open FSharp.Compiler.Symbols
open FSharp.Compiler.Tokenization
open System.Composition
open System.Text.RegularExpressions
open CancellableTasks
open Microsoft.VisualStudio.FSharp.Editor.Telemetry
open Microsoft.VisualStudio.Telemetry

module private Symbol =
let fullName (root: ISymbol) : string =
Expand Down Expand Up @@ -679,6 +680,9 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService,
// Task.Wait throws an exception if the task is cancelled, so be sure to catch it.
try
// This call to Wait() is fine because we want to be able to provide the error message in the status bar.
use _ =
TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||])

gtdTask.Wait(cancellationToken)

if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then
Expand All @@ -695,6 +699,7 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService,
statusBar.TempMessage(SR.CannotDetermineSymbol())
false
with exc ->
TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinition, FaultSeverity.General, exc)
statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc))

// Don't show the dialog box as it's most likely that the user cancelled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ open Microsoft.VisualStudio.Language.Intellisense
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Editor
open Microsoft.VisualStudio.Utilities
open Microsoft.VisualStudio.FSharp.Editor.Telemetry
open Microsoft.VisualStudio.Telemetry

[<AllowNullLiteral>]
type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpan, gtd: GoToDefinition) =
Expand Down Expand Up @@ -52,6 +54,9 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) =
// Task.Wait throws an exception if the task is cancelled, so be sure to catch it.
try
// This call to Wait() is fine because we want to be able to provide the error message in the status bar.
use _ =
TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinitionGetSymbol, [||])

gtdTask.Wait(cancellationToken)
statusBar.Clear()

Expand Down Expand Up @@ -88,6 +93,8 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) =
// The NavigableSymbols API accepts 'null' when there's nothing to navigate to.
return null
with exc ->
TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinitionGetSymbol, FaultSeverity.General, exc)

statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc))

// The NavigableSymbols API accepts 'null' when there's nothing to navigate to.
Expand Down
44 changes: 34 additions & 10 deletions vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ module TelemetryEvents =
let LanguageServiceStarted = "languageservicestarted"

[<Literal>]
let GetSymbolUsesInProjectsStarted = "getSymbolUsesInProjectsStarted"
let GetSymbolUsesInProjectsStarted = "getsymbolusesinprojectsstarted"

[<Literal>]
let GetSymbolUsesInProjectsFinished = "getSymbolUsesInProjectsFinished"
let GetSymbolUsesInProjectsFinished = "getsymbolusesinprojectsfinished"

[<Literal>]
let AddSyntacticCalssifications = "addsyntacticclassifications"
Expand All @@ -42,6 +42,12 @@ module TelemetryEvents =
[<Literal>]
let ProvideCompletions = "providecompletions"

[<Literal>]
let GoToDefinition = "gotodefinition"

[<Literal>]
let GoToDefinitionGetSymbol = "gotodefinition/getsymbol"

// TODO: needs to be something more sophisticated in future
[<Struct; RequireQualifiedAccess; NoComparison; NoEquality>]
type TelemetryThrottlingStrategy =
Expand Down Expand Up @@ -92,26 +98,44 @@ type TelemetryReporter private (name: string, props: (string * obj) array, stopw
(let componentModel =
Package.GetGlobalService(typeof<ComponentModelHost.SComponentModel>) :?> ComponentModelHost.IComponentModel

if componentModel = null then
if isNull componentModel then
TelemetryService.DefaultSession.IsUserMicrosoftInternal
else
let workspace = componentModel.GetService<VisualStudioWorkspace>()
workspace.Services.GetService<EditorOptions>().Advanced.SendAdditionalTelemetry)

TelemetryService.DefaultSession.IsUserMicrosoftInternal
|| workspace.Services.GetService<EditorOptions>().Advanced.SendAdditionalTelemetry)

static member ReportFault(name, ?severity: FaultSeverity, ?e: exn) =
if TelemetryReporter.SendAdditionalTelemetry.Value then
let faultName = String.Concat(name, "/fault")

match severity, e with
| Some s, Some e -> TelemetryService.DefaultSession.PostFault(faultName, name, s, e)
| None, Some e -> TelemetryService.DefaultSession.PostFault(faultName, name, e)
| Some s, None -> TelemetryService.DefaultSession.PostFault(faultName, name, s)
| None, None -> TelemetryService.DefaultSession.PostFault(faultName, name)
|> ignore

static member ReportCustomFailure(name, ?props) =
if TelemetryReporter.SendAdditionalTelemetry.Value then
let props = defaultArg props [||]
let name = String.Concat(name, "/failure")
let event = TelemetryReporter.createEvent name props
TelemetryService.DefaultSession.PostEvent event

static member ReportSingleEvent(name, props) =
let event = TelemetryReporter.createEvent name props
TelemetryService.DefaultSession.PostEvent event
if TelemetryReporter.SendAdditionalTelemetry.Value then
let event = TelemetryReporter.createEvent name props
TelemetryService.DefaultSession.PostEvent event

// A naïve implementation using stopwatch and returning an IDisposable
// TODO: needs a careful review, since it will be a hot path when we are sending telemetry
static member ReportSingleEventWithDuration(name, props, ?throttlingStrategy) : IDisposable =

let additionalTelemetryEnabled = TelemetryReporter.SendAdditionalTelemetry.Value

let isUserMicrosoftInternal =
TelemetryService.DefaultSession.IsUserMicrosoftInternal

if additionalTelemetryEnabled || isUserMicrosoftInternal then
if additionalTelemetryEnabled then
let throttlingStrategy =
defaultArg throttlingStrategy TelemetryThrottlingStrategy.Default

Expand Down