Skip to content

Commit

Permalink
update environments telemetry to add hresults (#3804)
Browse files Browse the repository at this point in the history
* update telemetry to add hresults

* update based on comments

* Fix whitespace

---------

Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com>
  • Loading branch information
bbonaby and krschau authored Sep 10, 2024
1 parent bd0858c commit a1d7c69
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 55 deletions.
8 changes: 6 additions & 2 deletions common/Environments/Models/CreateComputeSystemOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using DevHome.Common.Environments.Helpers;
using DevHome.Common.TelemetryEvents;
using DevHome.Common.TelemetryEvents.SetupFlow.Environments;
using DevHome.Telemetry;
using Microsoft.Windows.DevHome.SDK;
Expand Down Expand Up @@ -127,11 +128,14 @@ public void StartOperation()
Completed?.Invoke(this, CreateComputeSystemResult);
}
var (displayMessage, diagnosticText, telemetryStatus) = ComputeSystemHelpers.LogResult(CreateComputeSystemResult?.Result, _log);
var (_, _, telemetryStatus) = ComputeSystemHelpers.LogResult(CreateComputeSystemResult?.Result, _log);
var telemetryResult = new TelemetryResult(CreateComputeSystemResult?.Result);
var providerId = ProviderDetails.ComputeSystemProvider.Id;
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_Creation_Event",
LogLevel.Critical,
new EnvironmentCreationEvent(ProviderDetails.ComputeSystemProvider.Id, telemetryStatus, displayMessage, diagnosticText),
new EnvironmentCreationEvent(ProviderDetails.ComputeSystemProvider.Id, telemetryStatus, telemetryResult),
_activityId);
RemoveEventHandlers();
Expand Down
1 change: 1 addition & 0 deletions common/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ GetWindowRect
GetMonitorInfo
SetWindowPos
MonitorFromWindow
E_INVALIDARG
16 changes: 8 additions & 8 deletions common/TelemetryEvents/Environments/EnvironmentOperationEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,29 @@ public class EnvironmentOperationEvent : EventBase

public string? DiagnosticText { get; }

public int HResult { get; }

/// <summary>
/// Initializes a new instance of the <see cref="EnvironmentOperationEvent"/> class.
/// </summary>
/// <param name="status">The status of the compute system operation</param>
/// <param name="computeSystemOperation">An enum representing the compute system operation that was invoked</param>
/// <param name="providerId">The Id of the compute system provider that owns the compute system that is being launched</param>
/// <param name="additionalContext">The context in which the operation is running as. E.g the Pin to start operation can be for Pinning or Unpinning</param>
/// <param name="displayMessage">Associated error text that was displayed to the user</param>
/// <param name="diagnosticText">Associated error text for the operation</param>
/// <param name="result">Associated telemtry result for the operation</param>
public EnvironmentOperationEvent(
EnvironmentsTelemetryStatus status,
ComputeSystemOperations computeSystemOperation,
string providerId,
string? additionalContext = null,
string? displayMessage = null,
string? diagnosticText = null)
TelemetryResult result,
string? additionalContext = null)
{
Status = status.ToString();
OperationName = computeSystemOperation.ToString();
ProviderId = providerId;
HResult = result.HResult;
DisplayMessage = result.DisplayMessage;
DiagnosticText = result.DiagnosticText;
AdditionalContext = additionalContext;
DisplayMessage = displayMessage;
DiagnosticText = diagnosticText;
}

public override void ReplaceSensitiveStrings(Func<string, string> replaceSensitiveStrings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,26 @@ public class EnvironmentCreationEvent : EventBase

public string? DisplayMessage { get; }

public string? DiagnosticText { get; }

public string? DiagnosticText { get; }

public int HResult { get; }

/// <summary>
/// Initializes a new instance of the <see cref="EnvironmentCreationEvent"/> class.
/// </summary>
/// <param name="providerId">The Id of the compute system provider that initiated the creation operation</param>
/// <param name="status">The status of the creation operation</param>
/// <param name="diagnosticText">Associated error text for the operation</param>
public EnvironmentCreationEvent(string providerId, EnvironmentsTelemetryStatus status, string? displayMessage = null, string? diagnosticText = null)
/// <param name="status">The status of the creation operation</param>
/// <param name="result">Associated telemetry result for the operation</param>
public EnvironmentCreationEvent(
string providerId,
EnvironmentsTelemetryStatus status,
TelemetryResult result)
{
ProviderId = providerId;
Status = status.ToString();
DisplayMessage = displayMessage;
DiagnosticText = diagnosticText;
Status = status.ToString();
HResult = result.HResult;
DisplayMessage = result.DisplayMessage;
DiagnosticText = result.DiagnosticText;
}

// Inherited but unused.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@ public class EnvironmentLaunchEvent : EventBase

public string? DiagnosticText { get; }

public int HResult { get; }

/// <summary>
/// Initializes a new instance of the <see cref="EnvironmentLaunchEvent"/> class.
/// </summary>
/// <param name="providerId">The Id of the compute system provider that owns the compute system that is being launched</param>
/// <param name="status">The status of the launch operation</param>
/// <param name="diagnosticText">Associated error text for the operation</param>
public EnvironmentLaunchEvent(string providerId, EnvironmentsTelemetryStatus status, string? displayMessage = null, string? diagnosticText = null)
/// <param name="result">Associated telemetry result for the operation</param>
public EnvironmentLaunchEvent(
string providerId,
EnvironmentsTelemetryStatus status,
TelemetryResult result)
{
ProviderId = providerId;
Status = status.ToString();
DisplayMessage = displayMessage;
DiagnosticText = diagnosticText;
HResult = result.HResult;
DisplayMessage = result.DisplayMessage;
DiagnosticText = result.DiagnosticText;
}

// Inherited but unused.
Expand Down
63 changes: 63 additions & 0 deletions common/TelemetryEvents/TelemetryResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Windows.DevHome.SDK;
using Windows.Win32.Foundation;

namespace DevHome.Common.TelemetryEvents;

public class TelemetryResult
{
private const int SuccessHResult = 0;

public int HResult { get; private set; }

public ProviderOperationStatus Status { get; private set; }

public string? DisplayMessage { get; private set; }

public string? DiagnosticText { get; private set; }

public TelemetryResult(ProviderOperationResult? result)
{
UpdateProperties(result);
}

public TelemetryResult(int hResult, string displayMessage, string diagnosticText)
{
HResult = hResult;
Status = ProviderOperationStatus.Failure;
DisplayMessage = displayMessage;
DiagnosticText = diagnosticText;
}

public TelemetryResult()
{
HResult = SuccessHResult;
Status = ProviderOperationStatus.Success;
}

private void UpdateProperties(ProviderOperationResult? result)
{
if (result == null)
{
// The extension provided us with a null ProviderOperationResult,
// so the telemetry should state this explicitly.
Status = ProviderOperationStatus.Failure;
HResult = HRESULT.E_INVALIDARG;
DiagnosticText = "ProviderOperationResult was null";
DisplayMessage = "ProviderOperationResult was null";
return;
}

Status = result.Status;
DiagnosticText = result.DiagnosticText;
DisplayMessage = result.DisplayMessage;
HResult = SuccessHResult;

if (Status == ProviderOperationStatus.Failure)
{
HResult = result.ExtendedError.HResult;
}
}
}
43 changes: 43 additions & 0 deletions docs/tools/common/TelemetryResult.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# TelemetryResult
Used to extract data from a `ProviderOperationResult` object returned by an SDK method or wrapper class. This can also be used as a general class to store error information that can be sent in a telemetry payload.

## Properties
| Property | Type | Description |
| -------- | -------- | -------- |
| HResult | Integer | HRESULT code returned by an operation to Dev Home or an extension.
| Status | ProviderOperationStatus | Enum used to dictate whether an operation succeeded or failed. |
| DisplayMessage | String? | Optional display message returned to the user from an interaction with Dev Home. This may be localized. |
| DiagnosticText | String? | Optional diagnostic text return by an operation. This can be used to gain insights into errors that occur in an extension, after an operation is initiated by Dev Home. |

## Usage
#### ComputeSystemViewModel.cs
```C#
// Using the parameterless constructor. This is optional. Most events in Dev Home only care about the result of the operation and not that the operation started.
var startLaunchEvent = new EnvironmentLaunchEvent(
ComputeSystemProviderId,
EnvironmentsTelemetryStatus.Started,
new TelemetryResult());

// Send the telemetry stating that the operation has started.
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_Launch_Event",
LogLevel.Critical,
startLaunchEvent);

// It's good practise to wrap the out of proc COM object in a wrapper class and return the associated SDK result. Exceptions caught by the wrapper can then be used to return an SDK result that indicates that the operation failed.
var launchResponse = await ComputeSystemWrapper.ConnectAsync(string.Empty);

// ... Handle any post operation logic..
// Use the TelemetryResult to extract data from the ProviderOperationResult, so it can be used in an Event payload.
var endLaunchEvent = new EnvironmentLaunchEvent(
ComputeSystemProviderId,
telemetryStatusBasedOnResponse,
new TelemetryResult(launchResponse?.Result));

// Operation is completed and the payload for ending the event can be sent.
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_Launch_Event",
LogLevel.Critical,
endLaunchEvent);
```
33 changes: 31 additions & 2 deletions tools/Environments/DevHome.Environments/Helpers/DataExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
using System.Threading.Tasks;
using DevHome.Common.Environments.Models;
using DevHome.Common.Services;
using DevHome.Common.TelemetryEvents;
using DevHome.Common.TelemetryEvents.Environments;
using DevHome.Common.TelemetryEvents.SetupFlow.Environments;
using DevHome.Environments.Models;
using DevHome.Environments.ViewModels;
using DevHome.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.Windows.DevHome.SDK;

namespace DevHome.Environments.Helpers;

public class DataExtractor
{
private static readonly string _pinnedToStartStatusContext = "PinnedToStartMenuStatus";

private static readonly string _pinnedToTaskBarStatusContext = "PinnedToTaskBarStatus";

private static readonly StringResource _stringResource = new("DevHome.Environments.pri", "DevHome.Environments/Resources");

/// <summary>
Expand Down Expand Up @@ -48,12 +56,15 @@ public static List<OperationsViewModel> FillDotButtonOperations(ComputeSystemCac
public static async Task<List<PinOperationData>> FillDotButtonPinOperationsAsync(ComputeSystemCache computeSystem)
{
var supportedOperations = computeSystem.SupportedOperations.Value;
var providerId = computeSystem.AssociatedProviderId.Value;
var operationData = new List<PinOperationData>();
if (supportedOperations.HasFlag(ComputeSystemOperations.PinToTaskbar))
{
var pinResultTaskbar = await computeSystem.GetIsPinnedToTaskbarAsync();
OperationsViewModel? operation = null;
if (pinResultTaskbar.Result.Status == ProviderOperationStatus.Success)
var pinStatusSucceeded = pinResultTaskbar.Result.Status == ProviderOperationStatus.Success;

if (pinStatusSucceeded)
{
if (pinResultTaskbar.IsPinned)
{
Expand All @@ -67,14 +78,24 @@ public static async Task<List<PinOperationData>> FillDotButtonPinOperationsAsync
}
}

var telemetryStatus = pinStatusSucceeded ? EnvironmentsTelemetryStatus.Succeeded : EnvironmentsTelemetryStatus.Failed;
var telemetryResult = new TelemetryResult(pinResultTaskbar.Result);

TelemetryFactory.Get<ITelemetry>().Log(
"Environment_OperationInvoked_Event",
LogLevel.Critical,
new EnvironmentOperationEvent(telemetryStatus, ComputeSystemOperations.PinToTaskbar, providerId, telemetryResult, _pinnedToTaskBarStatusContext));

operationData.Add(new(operation, pinResultTaskbar));
}

if (supportedOperations.HasFlag(ComputeSystemOperations.PinToStartMenu))
{
var pinResultStartMenu = await computeSystem.GetIsPinnedToStartMenuAsync();
OperationsViewModel? operation = null;
if (pinResultStartMenu.Result.Status == ProviderOperationStatus.Success)
var pinStatusSucceeded = pinResultStartMenu.Result.Status == ProviderOperationStatus.Success;

if (pinStatusSucceeded)
{
if (pinResultStartMenu.IsPinned)
{
Expand All @@ -88,6 +109,14 @@ public static async Task<List<PinOperationData>> FillDotButtonPinOperationsAsync
}
}

var telemetryStatus = pinStatusSucceeded ? EnvironmentsTelemetryStatus.Succeeded : EnvironmentsTelemetryStatus.Failed;
var telemetryResult = new TelemetryResult(pinResultStartMenu.Result);

TelemetryFactory.Get<ITelemetry>().Log(
"Environment_OperationInvoked_Event",
LogLevel.Critical,
new EnvironmentOperationEvent(telemetryStatus, ComputeSystemOperations.PinToStartMenu, providerId, telemetryResult, _pinnedToStartStatusContext));

operationData.Add(new(operation, pinResultStartMenu));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using DevHome.Common.Environments.Models;
using DevHome.Common.Environments.Services;
using DevHome.Common.Services;
using DevHome.Common.TelemetryEvents;
using DevHome.Common.TelemetryEvents.Environments;
using DevHome.Common.TelemetryEvents.SetupFlow.Environments;
using DevHome.Environments.Helpers;
Expand Down Expand Up @@ -137,7 +138,9 @@ private async Task InitializeOperationDataAsync()
ShouldShowDotOperations = false;
ShouldShowSplitButton = false;

RegisterForAllOperationMessages(DataExtractor.FillDotButtonOperations(ComputeSystem, _mainWindow), DataExtractor.FillLaunchButtonOperations(_provider, ComputeSystem, _configurationAction));
RegisterForAllOperationMessages(
DataExtractor.FillDotButtonOperations(ComputeSystem, _mainWindow),
DataExtractor.FillLaunchButtonOperations(_provider, ComputeSystem, _configurationAction));

_ = Task.Run(async () =>
{
Expand Down Expand Up @@ -274,15 +277,16 @@ public void LaunchAction()
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_Launch_Event",
LogLevel.Critical,
new EnvironmentLaunchEvent(ComputeSystem.AssociatedProviderId.Value, EnvironmentsTelemetryStatus.Started));
new EnvironmentLaunchEvent(ComputeSystem.AssociatedProviderId.Value, EnvironmentsTelemetryStatus.Started, new TelemetryResult()));
var operationResult = await ComputeSystem.ConnectAsync(string.Empty);
var (displayMessage, diagnosticText, telemetryStatus) = ComputeSystemHelpers.LogResult(operationResult?.Result, _log);
var (displayMessage, _, telemetryStatus) = ComputeSystemHelpers.LogResult(operationResult?.Result, _log);
var telemetryResult = new TelemetryResult(operationResult?.Result);
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_Launch_Event",
LogLevel.Critical,
new EnvironmentLaunchEvent(ComputeSystem.AssociatedProviderId.Value, telemetryStatus, displayMessage, diagnosticText));
new EnvironmentLaunchEvent(ComputeSystem.AssociatedProviderId.Value, telemetryStatus, telemetryResult));
if (telemetryStatus == EnvironmentsTelemetryStatus.Failed)
{
Expand Down Expand Up @@ -324,10 +328,17 @@ public void Receive(ComputeSystemOperationStartedMessage message)
_log.Information($"operation '{data.ComputeSystemOperation}' starting for Compute System: {Name}");
var telemetryPayload = new EnvironmentOperationEvent(
EnvironmentsTelemetryStatus.Started,
data.ComputeSystemOperation,
providerId,
new TelemetryResult(),
data.AdditionalContext);
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_OperationInvoked_Event",
LogLevel.Measure,
new EnvironmentOperationEvent(EnvironmentsTelemetryStatus.Started, data.ComputeSystemOperation, providerId, data.AdditionalContext),
LogLevel.Critical,
telemetryPayload,
relatedActivityId: message.Value.ActivityId);
});
}
Expand All @@ -345,11 +356,12 @@ public void Receive(ComputeSystemOperationCompletedMessage message)
_log.Information($"operation '{data.ComputeSystemOperation}' completed for Compute System: {Name}");
var providerId = ComputeSystem.AssociatedProviderId.Value;
var (displayMessage, diagnosticText, telemetryStatus) = ComputeSystemHelpers.LogResult(data.OperationResult.Result, _log);
var (displayMessage, _, telemetryStatus) = ComputeSystemHelpers.LogResult(data.OperationResult.Result, _log);
var telemetryResult = new TelemetryResult(data.OperationResult.Result);
TelemetryFactory.Get<ITelemetry>().Log(
"Environment_OperationInvoked_Event",
LogLevel.Measure,
new EnvironmentOperationEvent(telemetryStatus, data.ComputeSystemOperation, providerId, data.AdditionalContext, displayMessage, diagnosticText),
LogLevel.Critical,
new EnvironmentOperationEvent(telemetryStatus, data.ComputeSystemOperation, providerId, telemetryResult, data.AdditionalContext),
relatedActivityId: message.Value.ActivityId);
});
}
Expand Down
Loading

0 comments on commit a1d7c69

Please sign in to comment.