Skip to content
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

Improve DSC summary view loading performance #3397

Merged
merged 6 commits into from
Jul 24, 2024
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
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace DevHome.Services.DesiredStateConfiguration.Contracts;

Expand All @@ -13,10 +15,15 @@ public interface IDSCUnit
public string Type { get; }

/// <summary>
/// Gets an identifier used to uniquely identify the instance of a configuration unit on the system.
/// Gets the identifier name of this instance within the set.
/// </summary>
public string Id { get; }

/// <summary>
/// Gets an identifier used to uniquely identify the instance of a configuration unit on the system.
/// </summary>
public Guid InstanceId { get; }

/// <summary>
/// Gets a description of the configuration unit.
/// </summary>
Expand All @@ -27,6 +34,11 @@ public interface IDSCUnit
/// </summary>
public string Intent { get; }

/// <summary>
/// Gets the name of the module that this configuration unit belongs to.
/// </summary>
public string ModuleName { get; }

/// <summary>
/// Gets the <see cref="Id"/> values of the configuration units that this unit depends on.
/// </summary>
Expand All @@ -43,7 +55,8 @@ public interface IDSCUnit
public IList<KeyValuePair<string, string>> Metadata { get; }

/// <summary>
/// Gets the information on the origin of the configuration unit if available.
/// Gets the details of the configuration unit.
/// </summary>
public IDSCUnitDetails Details { get; }
/// <returns>The details of the configuration unit.</returns>
public Task<IDSCUnitDetails> GetDetailsAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ namespace DevHome.Services.DesiredStateConfiguration.Models;

internal sealed class DSCSet : IDSCSet
{
/// <inheritdoc/>
public Guid InstanceIdentifier { get; }

/// <inheritdoc/>
public string Name { get; }

/// <inheritdoc />
public IReadOnlyList<IDSCUnit> Units => UnitsInternal.AsReadOnly();

/// <summary>
/// Gets the list of units in this set.
/// </summary>
/// <remarks>
/// This list maintains the concrete type of the objects which is internal
/// to this service project.
/// </remarks>
internal IList<DSCUnit> UnitsInternal { get; }

public DSCSet(ConfigurationSet configSet)
{
// Constructor copies all the required data from the out-of-proc COM
Expand All @@ -19,12 +37,6 @@ public DSCSet(ConfigurationSet configSet)
// longer available (e.g. AppInstaller service is no longer running).
InstanceIdentifier = configSet.InstanceIdentifier;
Name = configSet.Name;
Units = configSet.Units.Select(unit => new DSCUnit(unit)).ToList();
UnitsInternal = configSet.Units.Select(unit => new DSCUnit(unit)).ToList();
}

public Guid InstanceIdentifier { get; }

public string Name { get; }

public IReadOnlyList<IDSCUnit> Units { get; }
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DevHome.Services.DesiredStateConfiguration.Contracts;
using Microsoft.Management.Configuration;

Expand All @@ -12,6 +14,35 @@ internal sealed class DSCUnit : IDSCUnit
{
private const string DescriptionMetadataKey = "description";
private const string ModuleMetadataKey = "module";
private readonly IDSCUnitDetails _defaultDetails;
private Task<IDSCUnitDetails> _loadDetailsTask;

/// <inheritdoc/>
public string Type { get; }

/// <inheritdoc/>
public string Id { get; }

/// <inheritdoc/>
public Guid InstanceId { get; }

/// <inheritdoc/>
public string Description { get; }

/// <inheritdoc/>
public string Intent { get; }

/// <inheritdoc/>
public string ModuleName { get; }

/// <inheritdoc/>
public IList<string> Dependencies { get; }

/// <inheritdoc/>
public IList<KeyValuePair<string, string>> Settings { get; }

/// <inheritdoc/>
public IList<KeyValuePair<string, string>> Metadata { get; }

public DSCUnit(ConfigurationUnit unit)
{
Expand All @@ -21,6 +52,7 @@ public DSCUnit(ConfigurationUnit unit)
// longer available (e.g. AppInstaller service is no longer running).
Type = unit.Type;
Id = unit.Identifier;
InstanceId = unit.InstanceIdentifier;
Intent = unit.Intent.ToString();
Dependencies = [.. unit.Dependencies];

Expand All @@ -32,33 +64,27 @@ public DSCUnit(ConfigurationUnit unit)
Settings = unit.Settings.Select(s => new KeyValuePair<string, string>(s.Key, s.Value.ToString())).ToList();
Metadata = unit.Metadata.Select(m => new KeyValuePair<string, string>(m.Key, m.Value.ToString())).ToList();

// Load details if available, otherwise create empty details with just
// the module name if available
if (unit.Details == null)
{
// Get module name from metadata
unit.Metadata.TryGetValue(ModuleMetadataKey, out var moduleObj);
Details = new DSCUnitDetails(moduleObj?.ToString() ?? string.Empty);
}
else
{
Details = new DSCUnitDetails(unit.Details);
}
}

public string Type { get; }
// Get module name from metadata
ModuleName = Metadata.FirstOrDefault(m => m.Key == ModuleMetadataKey).Value?.ToString() ?? string.Empty;

public string Id { get; }

public string Description { get; }

public string Intent { get; }

public IList<string> Dependencies { get; }

public IList<KeyValuePair<string, string>> Settings { get; }
// Build default details
_defaultDetails = unit.Details == null ? new DSCUnitDetails(ModuleName) : new DSCUnitDetails(unit.Details);
_loadDetailsTask = Task.FromResult(_defaultDetails);
}

public IList<KeyValuePair<string, string>> Metadata { get; }
/// <inheritdoc/>
public async Task<IDSCUnitDetails> GetDetailsAsync()
{
var loadedDetails = await _loadDetailsTask;
return loadedDetails ?? _defaultDetails;
}

public IDSCUnitDetails Details { get; }
/// <summary>
/// Set an asynchronous task to load the details for the unit in the background.
/// </summary>
/// <param name="loadDetailsTask">Task to load the details</param>
internal void SetLoadDetailsTask(Task<IDSCUnitDetails> loadDetailsTask)
{
_loadDetailsTask = loadDetailsTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Linq;
using System.Threading.Tasks;
using DevHome.Services.DesiredStateConfiguration.Contracts;
using DevHome.Services.DesiredStateConfiguration.Exceptions;
Expand Down Expand Up @@ -44,8 +45,32 @@ public async Task<IDSCSet> GetConfigurationUnitDetailsAsync(IDSCFile file)
var configSet = await OpenConfigurationSetAsync(file, processor);

_logger.LogInformation("Getting configuration unit details");
await processor.GetSetDetailsAsync(configSet, ConfigurationUnitDetailFlags.ReadOnly);
return new DSCSet(configSet);
var detailsOperation = processor.GetSetDetailsAsync(configSet, ConfigurationUnitDetailFlags.ReadOnly);
var detailsOperationTask = detailsOperation.AsTask();

var set = new DSCSet(configSet);

// For each DSC unit, create a task to get the details asynchronously
// in the background
foreach (var unit in set.UnitsInternal)
AmelBawa-msft marked this conversation as resolved.
Show resolved Hide resolved
{
unit.SetLoadDetailsTask(Task.Run<IDSCUnitDetails>(async () =>
{
try
{
await detailsOperationTask;
_logger.LogInformation($"Settings details for unit {unit.InstanceId}");
return GetCompleteUnitDetails(configSet, unit.InstanceId);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Failed to get details for unit {unit.InstanceId}");
return null;
}
}));
}

return set;
}

/// <inheritdoc />
Expand Down Expand Up @@ -157,4 +182,30 @@ private static async Task<InMemoryRandomAccessStream> StringToStreamAsync(string
result.Seek(0);
return result;
}

/// <summary>
/// Gets the complete details for a unit if available.
/// </summary>
/// <param name="configSet">Configuration set</param>
/// <param name="instanceId">Unit instance ID</param>
/// <returns>Complete unit details if available, otherwise null</returns>
private DSCUnitDetails GetCompleteUnitDetails(ConfigurationSet configSet, Guid instanceId)
{
var unitFound = configSet.Units.FirstOrDefault(u => u.InstanceIdentifier == instanceId);
if (unitFound == null)
{
_logger.LogWarning($"Unit {instanceId} not found in the configuration set. No further details will be available to the unit.");
return null;
}

if (unitFound.Details == null)
{
_logger.LogWarning($"Details for unit {instanceId} not found. No further details will be available to the unit.");
return null;
}

// After GetSetDetailsAsync completes, the Details property will be
// populated if the details were found.
return new DSCUnitDetails(unitFound.Details);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using DevHome.Services.DesiredStateConfiguration.Contracts;

namespace DevHome.SetupFlow.ViewModels;

public class DSCConfigurationUnitDetailsViewModel
{
private readonly IDSCUnitDetails _unitDetails;

public DSCConfigurationUnitDetailsViewModel(IDSCUnitDetails unitDetails)
{
_unitDetails = unitDetails;
}

public string UnitType => _unitDetails.UnitType;

public string UnitDescription => _unitDetails.UnitDescription;

public string UnitDocumentationUri => _unitDetails.UnitDocumentationUri;

public string ModuleName => _unitDetails.ModuleName;

public string ModuleType => _unitDetails.ModuleType;

public string ModuleSource => _unitDetails.ModuleSource;

public string ModuleDescription => _unitDetails.ModuleDescription;

public string ModuleDocumentationUri => _unitDetails.ModuleDocumentationUri;

public string PublishedModuleUri => _unitDetails.PublishedModuleUri;

public string Version => _unitDetails.Version;

public bool IsLocal => _unitDetails.IsLocal;

public string Author => _unitDetails.Author;

public string Publisher => _unitDetails.Publisher;

public bool IsPublic => _unitDetails.IsPublic;
}
Loading
Loading