winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 7ee678403a6cd082f73cae47be9a98bfda3c5973
parent 959aacb3cb6573931d6aea30f591dfeb8bfd872c
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date:   Tue, 20 Oct 2020 18:05:14 -0700

Telemetry reporting improvements (#617)


Diffstat:
Msrc/AppInstallerCLICore/Commands/InstallCommand.cpp | 4++++
Msrc/AppInstallerCLICore/Commands/UpgradeCommand.cpp | 7+++++++
Msrc/AppInstallerCLICore/Core.cpp | 3+++
Msrc/AppInstallerCLICore/ExecutionContext.h | 13+++++++++++++
Msrc/AppInstallerCLICore/Workflows/UpdateFlow.cpp | 6++++++
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 28++++++++++++++++++++++++++--
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.h | 25+++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/AppInstallerTelemetry.cpp | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/AppInstallerCommonCore/Manifest/YamlParser.cpp | 2+-
Msrc/AppInstallerCommonCore/Public/AppInstallerTelemetry.h | 28++++++++++++++++++++++++++--
10 files changed, 167 insertions(+), 7 deletions(-)

diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -97,13 +97,17 @@ namespace AppInstaller::CLI void InstallCommand::ExecuteInternal(Execution::Context& context) const { context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << Workflow::GetManifest << Workflow::EnsureMinOSVersion << Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << Workflow::ShowInstallationDisclaimer << + Workflow::ReportExecutionStage(ExecutionStage::Download) << Workflow::DownloadInstaller << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << Workflow::ExecuteInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << Workflow::RemoveInstaller; } } diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -116,6 +116,7 @@ namespace AppInstaller::CLI WI_SetFlag(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << OpenSource << OpenCompositeSource(Repository::PredefinedSource::Installed); @@ -149,8 +150,11 @@ namespace AppInstaller::CLI SelectInstaller << EnsureApplicableInstaller << ShowInstallationDisclaimer << + Workflow::ReportExecutionStage(ExecutionStage::Download) << DownloadInstaller << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << ExecuteInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << RemoveInstaller; } else @@ -182,8 +186,11 @@ namespace AppInstaller::CLI context << ShowInstallationDisclaimer << + Workflow::ReportExecutionStage(ExecutionStage::Download) << DownloadInstaller << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << ExecuteInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << RemoveInstaller; } } diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -4,6 +4,7 @@ #include "Public/AppInstallerCLICore.h" #include "Commands/RootCommand.h" #include "ExecutionContext.h" +#include "Workflows/WorkflowBase.h" #include <winget/UserSettings.h> using namespace winrt; @@ -61,6 +62,8 @@ namespace AppInstaller::CLI Execution::Context context{ std::cout, std::cin }; context.EnableCtrlHandler(); + context << Workflow::ReportExecutionStage(Workflow::ExecutionStage::ParseArgs); + // Convert incoming wide char args to UTF8 std::vector<std::string> utf8Args; for (int i = 1; i < argc; ++i) diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -33,6 +33,7 @@ namespace AppInstaller::CLI::Workflow { struct WorkflowTask; + enum class ExecutionStage : uint32_t; } namespace AppInstaller::CLI::Execution @@ -53,6 +54,7 @@ namespace AppInstaller::CLI::Execution InstallerArgs, CompletionData, InstalledPackageVersion, + ExecutionStage, Max }; @@ -139,6 +141,12 @@ namespace AppInstaller::CLI::Execution using value_t = std::shared_ptr<Repository::IPackageVersion>; }; + template <> + struct DataMapping<Data::ExecutionStage> + { + using value_t = Workflow::ExecutionStage; + }; + // Used to deduce the DataVariant type; making a variant that includes std::monostate and all DataMapping types. template <size_t... I> inline auto Deduce(std::index_sequence<I...>) { return std::variant<std::monostate, DataMapping<static_cast<Data>(I)>::value_t...>{}; } @@ -194,6 +202,11 @@ namespace AppInstaller::CLI::Execution { m_data[D].emplace<details::DataIndex(D)>(std::forward<typename details::DataMapping<D>::value_t>(v)); } + template <Data D> + void Add(const typename details::DataMapping<D>::value_t& v) + { + m_data[D].emplace<details::DataIndex(D)>(v); + } // Return a value indicating whether the given data type is stored in the context. bool Contains(Data d) { return (m_data.find(d) != m_data.end()); } diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -105,6 +105,8 @@ namespace AppInstaller::CLI::Workflow bool updateAllHasFailure = false; for (const auto& match : matches) { + Logging::SubExecutionTelemetryScope subExecution; + // We want to do best effort to update all applicable updates regardless on previous update failure auto updateContextPtr = context.Clone(); Execution::Context& updateContext = *updateContextPtr; @@ -113,10 +115,14 @@ namespace AppInstaller::CLI::Workflow updateContext.Add<Execution::Data::InstalledPackageVersion>(match.Package->GetInstalledVersion()); updateContext << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << SelectLatestApplicableUpdate(*(match.Package)) << ShowInstallationDisclaimer << + Workflow::ReportExecutionStage(ExecutionStage::Download) << DownloadInstaller << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << ExecuteInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << RemoveInstaller; if (updateContext.GetTerminationHR() != S_OK && diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -458,7 +458,7 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); } - Logging::Telemetry().LogManifestFields(manifest->Id, manifest->Name, manifest->Version, false); + Logging::Telemetry().LogManifestFields(manifest->Id, manifest->Name, manifest->Version); context.Add<Execution::Data::Manifest>(std::move(manifest.value())); } @@ -481,12 +481,14 @@ namespace AppInstaller::CLI::Workflow void GetManifestFromArg(Execution::Context& context) { + Logging::Telemetry().LogIsManifestLocal(true); + context << VerifyFile(Execution::Args::Type::Manifest) << [](Execution::Context& context) { Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::Manifest))); - Logging::Telemetry().LogManifestFields(manifest.Id, manifest.Name, manifest.Version, true); + Logging::Telemetry().LogManifestFields(manifest.Id, manifest.Name, manifest.Version); context.Add<Execution::Data::Manifest>(std::move(manifest)); }; } @@ -600,6 +602,28 @@ namespace AppInstaller::CLI::Workflow const auto& searchResult = context.Get<Execution::Data::SearchResult>(); context.Add<Execution::Data::InstalledPackageVersion>(searchResult.Matches.at(0).Package->GetInstalledVersion()); } + + void ReportExecutionStage::operator()(Execution::Context& context) const + { + if (!context.Contains(Execution::Data::ExecutionStage)) + { + context.Add<Execution::Data::ExecutionStage>(m_stage); + } + else if (context.Get<Execution::Data::ExecutionStage>() == m_stage) + { + return; + } + else if (context.Get<Execution::Data::ExecutionStage>() < m_stage || m_allowBackward) + { + context.Get<Execution::Data::ExecutionStage>() = m_stage; + } + else + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), "Reporting ExecutionStage to an earlier Stage without allowBackward as true"); + } + + Logging::SetExecutionStage(static_cast<uint32_t>(context.Get<Execution::Data::ExecutionStage>())); + } } AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -18,6 +18,16 @@ namespace AppInstaller::CLI::Workflow { static const char* s_InstallationMetadata_Key_InstallerType = "InstallerType"; + // Values are ordered in a typical workflow stages + enum class ExecutionStage : uint32_t + { + ParseArgs = 1000, + Discovery = 2000, + Download = 3000, + Execution = 4000, + PostExecution = 5000, + }; + // A task in the workflow. struct WorkflowTask { @@ -233,6 +243,21 @@ namespace AppInstaller::CLI::Workflow // Inputs: SearchResult // Outputs: InstalledPackageVersion void GetInstalledPackageVersion(Execution::Context& context); + + // Reports execution stage in a workflow + // Required Args: ExecutionStage + // Inputs: ExecutionStage? + // Outputs: ExecutionStage + struct ReportExecutionStage : public WorkflowTask + { + ReportExecutionStage(ExecutionStage stage, bool allowBackward = false) : WorkflowTask("ReportExecutionStage"), m_stage(stage), m_allowBackward(allowBackward) {} + + void operator()(Execution::Context& context) const override; + + private: + ExecutionStage m_stage; + bool m_allowBackward; + }; } // Passes the context to the function if it has not been terminated; returns the context. diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -32,9 +32,15 @@ namespace AppInstaller::Logging namespace { + static const uint32_t s_RootExecutionId = 0; + // Used to disable telemetry on the fly. std::atomic_bool s_isTelemetryEnabled{ true }; + std::atomic_uint32_t s_executionStage{ 0 }; + + std::atomic_uint32_t s_subExecutionId{ s_RootExecutionId }; + bool IsTelemetryEnabled() { return g_IsTelemetryProviderEnabled && s_isTelemetryEnabled; @@ -83,6 +89,7 @@ namespace AppInstaller::Logging "FailureInfo", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), TraceLoggingHResult(failure.hr, "HResult"), TraceLoggingWideString(failure.pszMessage, "Message"), TraceLoggingString(failure.pszModule, "Module"), @@ -90,6 +97,7 @@ namespace AppInstaller::Logging TraceLoggingUInt32(static_cast<uint32_t>(failure.type), "Type"), TraceLoggingString(failure.pszFile, "File"), TraceLoggingUInt32(failure.uLineNumber, "Line"), + TraceLoggingUInt32(s_executionStage, "ExecutionStage"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } @@ -172,9 +180,11 @@ namespace AppInstaller::Logging "CommandTermination", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), TraceLoggingHResult(hr, "HResult"), AICLI_TraceLoggingStringView(file, "File"), TraceLoggingUInt64(static_cast<UINT64>(line), "Line"), + TraceLoggingUInt32(s_executionStage, "ExecutionStage"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } @@ -190,9 +200,11 @@ namespace AppInstaller::Logging "Exception", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), AICLI_TraceLoggingStringView(commandName, "Command"), AICLI_TraceLoggingStringView(type, "Type"), AICLI_TraceLoggingStringView(message, "Message"), + TraceLoggingUInt32(s_executionStage, "ExecutionStage"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } @@ -200,7 +212,22 @@ namespace AppInstaller::Logging AICLI_LOG(CLI, Error, << "Caught " << type << ": " << message); } - void TelemetryTraceLogger::LogManifestFields(std::string_view id, std::string_view name, std::string_view version, bool localManifest) noexcept + void TelemetryTraceLogger::LogIsManifestLocal(bool isLocalManifest) noexcept + { + if (IsTelemetryEnabled()) + { + TraceLoggingWriteActivity(g_hTelemetryProvider, + "GetManifest", + GetActivityId(), + nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), + TraceLoggingBool(isLocalManifest, "IsManifestLocal"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + } + + void TelemetryTraceLogger::LogManifestFields(std::string_view id, std::string_view name, std::string_view version) noexcept { if (IsTelemetryEnabled()) { @@ -208,10 +235,10 @@ namespace AppInstaller::Logging "ManifestFields", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), AICLI_TraceLoggingStringView(id, "Id"), AICLI_TraceLoggingStringView(name,"Name"), AICLI_TraceLoggingStringView(version, "Version"), - TraceLoggingBool(localManifest, "IsManifestLocal"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance|PDT_ProductAndServiceUsage), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } @@ -227,6 +254,7 @@ namespace AppInstaller::Logging "NoAppMatch", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } @@ -242,6 +270,7 @@ namespace AppInstaller::Logging "MultiAppMatch", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); } @@ -257,6 +286,7 @@ namespace AppInstaller::Logging "AppFound", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), AICLI_TraceLoggingStringView(name, "Name"), AICLI_TraceLoggingStringView(id, "Id"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), @@ -274,6 +304,7 @@ namespace AppInstaller::Logging "SelectedInstaller", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), TraceLoggingInt32(arch, "Arch"), AICLI_TraceLoggingStringView(url, "Url"), AICLI_TraceLoggingStringView(installerType, "InstallerType"), @@ -308,6 +339,7 @@ namespace AppInstaller::Logging "SearchRequest", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), AICLI_TraceLoggingStringView(type, "Type"), AICLI_TraceLoggingStringView(query, "Query"), AICLI_TraceLoggingStringView(id, "Id"), @@ -330,6 +362,7 @@ namespace AppInstaller::Logging "SearchResultCount", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), TraceLoggingUInt64(resultCount, "ResultCount"), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); @@ -350,6 +383,7 @@ namespace AppInstaller::Logging "HashMismatch", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), AICLI_TraceLoggingStringView(id, "Id"), AICLI_TraceLoggingStringView(version, "Version"), AICLI_TraceLoggingStringView(channel, "Channel"), @@ -376,6 +410,7 @@ namespace AppInstaller::Logging "InstallerFailure", GetActivityId(), nullptr, + TraceLoggingUInt32(s_subExecutionId, "SubExecutionId"), AICLI_TraceLoggingStringView(id, "Id"), AICLI_TraceLoggingStringView(version, "Version"), AICLI_TraceLoggingStringView(channel, "Channel"), @@ -405,4 +440,23 @@ namespace AppInstaller::Logging s_isTelemetryEnabled = true; } } + + void SetExecutionStage(uint32_t stage) + { + s_executionStage = stage; + } + + std::atomic_uint32_t SubExecutionTelemetryScope::m_sessionId{ s_RootExecutionId }; + + SubExecutionTelemetryScope::SubExecutionTelemetryScope() + { + auto expected = s_RootExecutionId; + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !s_subExecutionId.compare_exchange_strong(expected, ++m_sessionId), + "Cannot create a sub execution telemetry session when a previous session exists."); + } + + SubExecutionTelemetryScope::~SubExecutionTelemetryScope() + { + s_subExecutionId = s_RootExecutionId; + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -433,7 +433,7 @@ namespace AppInstaller::Manifest { {ManifestInstaller::InstallerSwitchType::Silent, ManifestInstaller::string_t("/S")}, {ManifestInstaller::InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/S")}, - {ManifestInstaller::InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/D=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} + {ManifestInstaller::InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/D=" + std::string(ARG_TOKEN_INSTALLPATH))} }; case ManifestInstaller::InstallerTypeEnum::Inno: return diff --git a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h @@ -44,9 +44,12 @@ namespace AppInstaller::Logging // Logs the invoked command termination. void LogException(std::string_view commandName, std::string_view type, std::string_view message) noexcept; + // Logs whether the manifest used in workflow is local + void LogIsManifestLocal(bool isLocalManifest) noexcept; + // Logs the Manifest fields. - void LogManifestFields(std::string_view id, std::string_view name, std::string_view version, bool localManifest) noexcept; - + void LogManifestFields(std::string_view id, std::string_view name, std::string_view version) noexcept; + // Logs when there is no matching App found for search void LogNoAppMatch() noexcept; @@ -116,4 +119,25 @@ namespace AppInstaller::Logging private: DestructionToken m_token; }; + + // Sets an execution stage to be reported when failures occur. + void SetExecutionStage(uint32_t stage); + + // An RAII object to log telemetry as sub execution. + // Does not support nested sub execution. + struct SubExecutionTelemetryScope + { + SubExecutionTelemetryScope(); + + SubExecutionTelemetryScope(const SubExecutionTelemetryScope&) = delete; + SubExecutionTelemetryScope& operator=(const SubExecutionTelemetryScope&) = delete; + + SubExecutionTelemetryScope(SubExecutionTelemetryScope&&) = default; + SubExecutionTelemetryScope& operator=(SubExecutionTelemetryScope&&) = default; + + ~SubExecutionTelemetryScope(); + + private: + static std::atomic_uint32_t m_sessionId; + }; }