diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2961c3bd5ff..117d0ddf276 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -46,10 +46,8 @@
-
-
-
-
+
+
diff --git a/NuGet.config b/NuGet.config
index c181d033061..764f9c8ddaa 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -18,11 +18,7 @@
-
-
-
-
diff --git a/THIRDPARTYNOTICES.txt b/THIRDPARTYNOTICES.txt
index 49e551d4279..982c50663a3 100644
--- a/THIRDPARTYNOTICES.txt
+++ b/THIRDPARTYNOTICES.txt
@@ -43,33 +43,3 @@ Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
-
--------------------------------
-
-Notice for OpenTelemetry .NET
--------------------------------
-MSBuild.exe is distributed with OpenTelemetry .NET binaries.
-
-Copyright (c) OpenTelemetry Authors
-Source: https://github.com/open-telemetry/opentelemetry-dotnet
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software distributed
-under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
-CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
-language governing permissions and limitations under the License.
-
--------------------------------
-
-Notice for Microsoft.VisualStudio.OpenTelemetry.*
--------------------------------
-MSBuild.exe is distributed with Microsoft.VisualStudio.OpenTelemetry.* binaries.
-
-Project: Microsoft.VisualStudio.OpenTelemetry
-Copyright: (c) Microsoft Corporation
-License: https://visualstudio.microsoft.com/license-terms/mt736442/
\ No newline at end of file
diff --git a/documentation/specs/VS-OpenTelemetry.md b/documentation/specs/VS-OpenTelemetry.md
deleted file mode 100644
index 59d1f6e5d17..00000000000
--- a/documentation/specs/VS-OpenTelemetry.md
+++ /dev/null
@@ -1,198 +0,0 @@
-# Telemetry via OpenTelemetry design
-
-VS OTel provide packages compatible with ingesting data to their backend if we instrument it via OpenTelemetry traces (System.Diagnostics.Activity).
-VS OTel packages are not open source so we need to conditionally include them in our build only for VS and MSBuild.exe
-
-> this formatting is a comment describing how the implementation turned out in 17.14 when our original goals were different
-
-[Onepager](https://github.com/dotnet/msbuild/blob/main/documentation/specs/proposed/telemetry-onepager.md)
-
-## Concepts
-
-It's a bit confusing how things are named in OpenTelemetry and .NET and VS Telemetry and what they do.
-
-| OTel concept | .NET/VS | Description |
-| --- | --- | --- |
-| Span/Trace | System.Diagnostics.Activity | Trace is a tree of Spans. Activities can be nested.|
-| Tracer | System.Diagnostics.ActivitySource | Creates activites. |
-| Processor/Exporter | VS OTel provided default config | filters and saves telemetry as files in a desired format |
-| TracerProvider | OTel SDK TracerProvider | Singleton that is aware of processors, exporters and Tracers and listens (in .NET a bit looser relationship because it does not create Tracers just hooks to them) |
-| Collector | VS OTel Collector | Sends to VS backend |
-
-## Requirements
-
-### Performance
-
-- If not sampled, no infra initialization overhead.
-- Avoid allocations when not sampled.
-- Has to have no impact on Core without opting into tracing, small impact on Framework
-- No regression in VS perf ddrit scenarios.
-
-> there is an allocation regression when sampled, one of the reasons why it's not enabled by default
-
-### Privacy
-
-- Hashing data points that could identify customers (e.g. names of targets)
-- Opt out capability
-
-### Security
-
-- Providing or/and documenting a method for creating a hook in Framework MSBuild
-- If custom hooking solution will be used - document the security implications of hooking custom telemetry Exporters/Collectors in Framework
-- other security requirements (transportation, rate limiting, sanitization, data access) are implemented by VS Telemetry library or the backend
-
-> hooking in Framework not implemented
-
-### Data handling
-
-- Implement head [Sampling](https://opentelemetry.io/docs/concepts/sampling/) with the granularity of a MSBuild.exe invocation/VS instance.
-- VS Data handle tail sampling in their infrastructure not to overwhelm storage with a lot of build events.
-
-#### Data points
-
-The data sent via VS OpenTelemetry is neither a subset neither a superset of what is sent to SDK telemetry and it is not a purpose of this design to unify them.
-
-##### Basic info
-
-- Build duration
-- Host
-- Build success/failure
-- Version
-- Target (hashed)
-
-##### Evnironment
-
-- SAC (Smart app control) enabled
-
-##### Features
-
-- BuildCheck enabled
-- Tasks runtimes and memory usage
-- Tasks summary - whether they come from Nuget or are custom
-- Targets summary - how many loaded and executed, how many come from nuget, how many come from metaproject
-
-The design should allow for easy instrumentation of additional data points.
-> current implementation has only one datapoint and that is the whole build `vs/msbuild/build`, the instrumentaiton of additional datapoints is gated by first checking that telemetry is running and using `Activity` classes only in helper methods gated by `[MethodImpl(MethodImplOptions.NoInlining)]` to avoid System.Diagnostics.DiagnosticSource dll load.
-
-## Core `dotnet build` scenario
-
-- Telemetry should not be collected via VS OpenTelemetry mechanism because it's already collected in sdk.
-- opt in to initialize the ActivitySource to avoid degrading performance.
-- [baronfel/otel-startup-hook: A .NET CLR Startup Hook that exports OpenTelemetry metrics via the OTLP Exporter to an OpenTelemetry Collector](https://github.com/baronfel/otel-startup-hook/) and similar enable collecting telemetry data locally by listening to the ActivitySource prefix defined in MSBuild.
-
-> this hook can be used when the customer specifies that they want to listen to the prefix `Microsoft.VisualStudio.OpenTelemetry.MSBuild`, opt in by setting environment variables `MSBUILD_TELEMETRY_OPTIN=1`,`MSBUILD_TELEMETRY_SAMPLE_RATE=1.0`
-
-## Standalone MSBuild.exe scenario
-
-- Initialize and finalize in Xmake.cs
- ActivitySource, TracerProvider, VS Collector
-- overhead of starting VS collector is nonzero
-- head sampling should avoid initializing if not sampled
-
-## VS in proc (devenv) scenario
-
-- VS can call `BuildManager` in a thread unsafe way the telemetry implementation has to be mindful of [BuildManager instances acquire its own BuildTelemetry instance by rokonec · Pull Request #8444 · dotnet/msbuild](https://github.com/dotnet/msbuild/pull/8444)
- - ensure no race conditions in initialization
- - only 1 TracerProvider with VS defined processing should exist
-- Visual Studio should be responsible for having a running collector, we don't want this overhead in MSBuild and eventually many will use it
-
-> this was not achieved in 17.14 so we start collector every time
-
-## Implementation and MSBuild developer experience
-
-### ActivitySource names
-
-- Microsoft.VisualStudio.OpenTelemetry.MSBuild.Default
-
-### Sampling
-
-Our estimation from VS and SDK data is that there are 10M-100M build events per day.
-For proportion estimation (of fairly common occurence in the builds), with not very strict confidnece (95%) and margin for error (5%) sampling 1:25000 would be enough.
-
-- this would apply for the DefaultActivitySource
-- other ActivitySources could be sampled more frequently to get enough data
-- Collecting has a cost, especially in standalone scenario where we have to start the collector. We might decide to undersample in standalone to avoid performance frequent impact.
-- We want to avoid that cost when not sampled, therefore we prefer head sampling.
-- Enables opt-in and opt-out for guaranteed sample or not sampled.
-- nullable ActivitySource, using `?` when working with them, we can be initialized but not sampled -> it will not reinitialize but not collect telemetry.
-
-- for 17.14 we can't use the new OTel assemblies and their dependencies, so everything has to be opt in.
-- eventually OpenTelemetry will be available and usable by default
-- We can use experiments in VS to pass the environment variable to initialize
-
-> Targeted notification can be set that samples 100% of customers to which it is sent
-
-### Initialization at entrypoints
-
-- There are 2 entrypoints:
- - for VS in BuildManager.BeginBuild
- - for standalone in Xmake.cs Main
-
-### Exiting
-
-Force flush TracerProvider's exporter in BuildManager.EndBuild.
-Dispose collector in Xmake.cs at the end of Main.
-
-### Configuration
-
-- Class that's responsible for configuring and initializing telemetry and handles optouts, holding tracer and collector.
-- Wrapping source so that it has correct prefixes for VS backend to ingest.
-
-### Instrumenting
-
-2 ways of instrumenting:
-
-#### Instrument areas in code running in the main process
-
-```csharp
-using (Activity? myActivity = OpenTelemetryManager.DefaultActivitySource?.StartActivity(TelemetryConstants.NameFromAConstantToAvoidAllocation))
-{
-// something happens here
-
-// add data to the trace
-myActivity?.WithTag("SpecialEvent","fail")
-}
-```
-
-Interface for classes holding telemetry data
-
-```csharp
-IActivityTelemetryDataHolder data = new SomeData();
-...
-myActivity?.WithTags(data);
-```
-
-> currently this should be gated in a separate method to avoid System.DiagnosticDiagnosticsource dll load.
-
-#### Default Build activity in EndBuild
-
-- this activity would always be created at the same point when sdk telemetry is sent in Core
-- we can add data to it that we want in general builds
-- the desired count of data from this should control the sample rate of DefaultActivitySource
-
-#### Multiple Activity Sources
-
-We want to create ActivitySources with different sample rates, this requires either implementation server side or a custom Processor.
-
-We potentially want apart from the Default ActivitySource:
-
-1. Other activity sources with different sample rates (in order to get significant data for rarer events such as custom tasks).
-2. a way to override sampling decision - ad hoc starting telemetry infrastructure to catch rare events
-
-- Create a way of using a "HighPrioActivitySource" which would override sampling and initialize Collector in MSBuild.exe scenario/tracerprovider in VS.
-- this would enable us to catch rare events
-
-> not implemented
-
-### Implementation details
-
-- `OpenTelemetryManager` - singleton that manages lifetime of OpenTelemetry objects listening to `Activity`ies, start by initializing in `Xmake` or `BuildManager`.
-- Task and Target data is forwarded from worker nodes via `TelemetryForwarder` and `InternalTelemetryForwardingLogger` and then aggregated to stats and serialized in `TelemetryDataUtils` and attached to the default `vs/msbuild/build` event.
-
-## Future work when/if we decide to invest in telemetry again
-
-- avoid initializing/finalizing collector in VS when there is one running
-- multiple levels of sampling for different types of events
-- running by default with head sampling (simplifies instrumentation with `Activity`ies)
-- implement anonymization consistently in an OTel processor and not ad hoc in each usage
-- add datapoints helping perf optimization decisions/ reliability investigations
diff --git a/documentation/specs/proposed/telemetry-onepager.md b/documentation/specs/proposed/telemetry-onepager.md
deleted file mode 100644
index 5bc8f22f9ce..00000000000
--- a/documentation/specs/proposed/telemetry-onepager.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# Telemetry
-
-We want to implement telemetry collection for VS/MSBuild.exe scenarios where we are currently not collecting data. VS OpenTelemetry initiative provides a good opportunity to use their infrastructure and library.
-There is some data we collect via SDK which we want to make accessible.
-
-## Goals and Motivation
-
-We have limited data about usage of MSBuild by our customers in VS and no data about usage of standalone msbuild.exe.
-This limits us in prioritization of features and scenarios to optimize performance for.
-Over time we want to have comprehensive insight into how MSBuild is used in all scenarios. Collecting such a data without any constraints nor limitations would however be prohibitively expensive (from the data storage PoV and possibly as well from the client side performance impact PoV). Ability to sample / configure the collection is an important factor in deciding the instrumentation and collection tech stack. Implementing telemetry via VS OpenTelemetry initiative would give us this ability in the future.
-
-Goal: To have relevant data in that is actionable for decisions about development. Measuring real world performance impact of features (e.g. BuildCheck). Easily extensible telemetry infrastructure if we want to measure a new datapoint.
-
-## Impact
-- Better planning of deployment of forces in MSBuild by product/team management.
-- Customers can subscribe to telemetry locally to have data in standardized OpenTelemetry format
-
-## Stakeholders
-- @Jan(Krivanek|Provaznik) design and implementation of telemetry via VS OTel. @ - using data we already have from SDK.
-- @maridematte - documenting + dashboarding currently existing datapoints.
-- MSBuild Team+Management – want insights from builds in VS
-- VS OpenTelemetry team – provide support for VS OpenTelemetry collector library, want successful adoption
-- SourceBuild – consulting and approving usage of OpenTelemetry
-- MSBuild PM @baronfel – representing customers who want to monitor their builds locally
-
-### V1 Successful handover
-- Shipped to Visual Studio
-- Data queryable in Kusto
-- Dashboards (even for pre-existing data - not introduced by this work)
-- Customers are able to monitor with OpenTelemetry collector of choice (can be cut)
-
-## Risks
-- Performance regression risks - it's another thing MSBuild would do and if the perf hit would be too bad it would need mitigation effort.
-- It introduces a closed source dependency for VS and MSBuild.exe distribution methods which requires workarounds to remain compatible with SourceBuild policy (conditional compilation/build).
-- Using a new VS API - might have gaps
-- storage costs
-- Potential additional costs and delays due to compliance with SourceBuild/VS data.
-
-## V1 Cost
-5 months of .5 developer's effort ~ 50 dev days (dd)
-
-20-30dd JanPro OTel design + implementation, 10-15dd JanK design + implementation, 5-10dd Mariana/someone getting available data in order/"data science"/dashboards + external documentation
-
-Uncertainties:
-It’s an exploratory project for VS OpenTelemetry, we'll be their first OSS component, so there might come up issues. SourceBuild compliance could introduce delays.
-
-## Plan
-### V1 scope
-- Collected data point definition
-- Instrumented data points (as an example how the instrumentation and collection works)
-- Telemetry sent to VS Telemetry in acceptable quantity
-- Dashboards for collected data
-- Hooking of customer's telemetry collection
-- Documenting and leveraging pre-existing telemetry
-
-#### Out of scope
-- Unifying telemetry for SDK MSBuild and MSBuild.exe/VS MSBuild.
-- Thorough instrumentation of MSBuild
-- Using MSBuild server
-- Distributed tracing
-
-### Detailed cost
-- Prototyping the libraries/mechanism for collecting telemetry data (month 1) 10dd
-
-- Defining usful data points (month 1) 5dd
-
-- Design and approval of hooking VSTelemetry collectors and OTel collectors (month 2) 10dd
-
-- Formalizing, agreeing to sourcebuild and other external requirements (month 2) 5dd
-
-- Instrumenting MSBuild with defined datapoints (month 3) 7dd
-
-- Creating dashboards/insights (month 4) 5dd
-
-- Documenting for customers how to hook their own telemetry collection (month 4) 3dd
-
-- Buffer for discovered issues (VSData Platform, SourceBuild, OpenTelemetry) and more investments (month 5) 5dd
diff --git a/eng/Signing.props b/eng/Signing.props
index b2e4bff8ffe..00e8367eb86 100644
--- a/eng/Signing.props
+++ b/eng/Signing.props
@@ -13,9 +13,6 @@
-
-
-
diff --git a/eng/Versions.props b/eng/Versions.props
index 5ab5660455d..74765ef67d5 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -59,8 +59,8 @@
- 0.2.104-beta
-
+ 17.14.18
+
diff --git a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
index 06cf011f5a2..ab4af5b59d6 100644
--- a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
+++ b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs
@@ -1801,7 +1801,6 @@ public void OverlappingBuildsOfTheSameProjectDifferentTargetsAreAllowed()
");
-
Project project = CreateProject(contents, MSBuildDefaultToolsVersion, _projectCollection, true);
ProjectInstance instance = _buildManager.GetProjectInstanceForBuild(project);
_buildManager.BeginBuild(_parameters);
diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
index 4ee55fb60ea..25ce6ea2727 100644
--- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
+++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj
@@ -30,10 +30,7 @@
all
-
-
-
-
+
diff --git a/src/Build.UnitTests/Telemetry/OpenTelemetryActivities_Tests.cs b/src/Build.UnitTests/Telemetry/OpenTelemetryActivities_Tests.cs
deleted file mode 100644
index 7a567e79495..00000000000
--- a/src/Build.UnitTests/Telemetry/OpenTelemetryActivities_Tests.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using Microsoft.Build.Framework.Telemetry;
-using Shouldly;
-using Xunit;
-
-namespace Microsoft.Build.Engine.UnitTests.Telemetry
-{
- public class ActivityExtensionsTests
- {
- [Fact]
- public void WithTag_ShouldSetUnhashedValue()
- {
- var activity = new Activity("TestActivity");
- activity.Start();
-
- var telemetryItem = new TelemetryItem(
- Name: "TestItem",
- Value: "TestValue",
- NeedsHashing: false);
-
- activity.WithTag(telemetryItem);
-
- var tagValue = activity.GetTagItem("VS.MSBuild.TestItem");
- tagValue.ShouldNotBeNull();
- tagValue.ShouldBe("TestValue");
-
- activity.Dispose();
- }
-
- [Fact]
- public void WithTag_ShouldSetHashedValue()
- {
- var activity = new Activity("TestActivity");
- var telemetryItem = new TelemetryItem(
- Name: "TestItem",
- Value: "SensitiveValue",
- NeedsHashing: true);
-
- activity.WithTag(telemetryItem);
-
- var tagValue = activity.GetTagItem("VS.MSBuild.TestItem");
- tagValue.ShouldNotBeNull();
- tagValue.ShouldNotBe("SensitiveValue"); // Ensure it’s not the plain text
- activity.Dispose();
- }
-
- [Fact]
- public void WithTags_ShouldSetMultipleTags()
- {
- var activity = new Activity("TestActivity");
- var tags = new List
- {
- new("Item1", "Value1", false),
- new("Item2", "Value2", true) // hashed
- };
-
- activity.WithTags(tags);
-
- var tagValue1 = activity.GetTagItem("VS.MSBuild.Item1");
- var tagValue2 = activity.GetTagItem("VS.MSBuild.Item2");
-
- tagValue1.ShouldNotBeNull();
- tagValue1.ShouldBe("Value1");
-
- tagValue2.ShouldNotBeNull();
- tagValue2.ShouldNotBe("Value2"); // hashed
-
- activity.Dispose();
- }
-
- [Fact]
- public void WithTags_DataHolderShouldSetMultipleTags()
- {
- var activity = new Activity("TestActivity");
- var dataHolder = new MockTelemetryDataHolder(); // see below
-
- activity.WithTags(dataHolder);
-
- var tagValueA = activity.GetTagItem("VS.MSBuild.TagA");
- var tagValueB = activity.GetTagItem("VS.MSBuild.TagB");
-
- tagValueA.ShouldNotBeNull();
- tagValueA.ShouldBe("ValueA");
-
- tagValueB.ShouldNotBeNull();
- tagValueB.ShouldNotBe("ValueB"); // should be hashed
- activity.Dispose();
- }
-
- [Fact]
- public void WithStartTime_ShouldSetActivityStartTime()
- {
- var activity = new Activity("TestActivity");
- var now = DateTime.UtcNow;
-
- activity.WithStartTime(now);
-
- activity.StartTimeUtc.ShouldBe(now);
- activity.Dispose();
- }
-
- [Fact]
- public void WithStartTime_NullDateTime_ShouldNotSetStartTime()
- {
- var activity = new Activity("TestActivity");
- var originalStartTime = activity.StartTimeUtc; // should be default (min) if not started
-
- activity.WithStartTime(null);
-
- activity.StartTimeUtc.ShouldBe(originalStartTime);
-
- activity.Dispose();
- }
- }
-
- ///
- /// A simple mock for testing IActivityTelemetryDataHolder.
- /// Returns two items: one hashed, one not hashed.
- ///
- internal sealed class MockTelemetryDataHolder : IActivityTelemetryDataHolder
- {
- public IList GetActivityProperties()
- {
- return new List
- {
- new("TagA", "ValueA", false),
- new("TagB", "ValueB", true),
- };
- }
- }
-
-
- public class MSBuildActivitySourceTests
- {
- [Fact]
- public void StartActivity_ShouldPrefixNameCorrectly_WhenNoRemoteParent()
- {
- var source = new MSBuildActivitySource(TelemetryConstants.DefaultActivitySourceNamespace, 1.0);
- using var listener = new ActivityListener
- {
- ShouldListenTo = activitySource => activitySource.Name == TelemetryConstants.DefaultActivitySourceNamespace,
- Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData,
- };
- ActivitySource.AddActivityListener(listener);
-
-
- var activity = source.StartActivity("Build");
-
- activity.ShouldNotBeNull();
- activity?.DisplayName.ShouldBe("VS/MSBuild/Build");
-
- activity?.Dispose();
- }
-
- [Fact]
- public void StartActivity_ShouldUseParentId_WhenRemoteParentExists()
- {
- // Arrange
- var parentActivity = new Activity("ParentActivity");
- parentActivity.SetParentId("|12345.abcde."); // Simulate some parent trace ID
- parentActivity.AddTag("sampleTag", "sampleVal");
- parentActivity.Start();
-
- var source = new MSBuildActivitySource(TelemetryConstants.DefaultActivitySourceNamespace, 1.0);
- using var listener = new ActivityListener
- {
- ShouldListenTo = activitySource => activitySource.Name == TelemetryConstants.DefaultActivitySourceNamespace,
- Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllData,
- };
- ActivitySource.AddActivityListener(listener);
-
- // Act
- var childActivity = source.StartActivity("ChildBuild");
-
- // Assert
- childActivity.ShouldNotBeNull();
- // If HasRemoteParent is true, the code uses `parentId: Activity.Current.ParentId`.
- // However, by default .NET Activity doesn't automatically set HasRemoteParent = true
- // unless you explicitly set it. If you have logic that sets it, you can test it here.
- // For demonstration, we assume the ParentId is carried over if HasRemoteParent == true.
- if (Activity.Current?.HasRemoteParent == true)
- {
- childActivity?.ParentId.ShouldBe("|12345.abcde.");
- }
-
- parentActivity.Dispose();
- childActivity?.Dispose();
- }
- }
-}
diff --git a/src/Build.UnitTests/Telemetry/OpenTelemetryManager_Tests.cs b/src/Build.UnitTests/Telemetry/OpenTelemetryManager_Tests.cs
deleted file mode 100644
index 3faa3ab54a9..00000000000
--- a/src/Build.UnitTests/Telemetry/OpenTelemetryManager_Tests.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Microsoft.Build.Execution;
-using Microsoft.Build.Framework.Telemetry;
-using Microsoft.Build.UnitTests;
-using Shouldly;
-using Xunit;
-
-namespace Microsoft.Build.Engine.UnitTests.Telemetry
-{
- // Putting the tests to a collection ensures tests run serially by default, that's needed to isolate the manager singleton state and env vars in some telemetry tests.
- [Collection("OpenTelemetryManagerTests")]
- public class OpenTelemetryManagerTests : IDisposable
- {
-
- private const string TelemetryFxOptoutEnvVarName = "MSBUILD_TELEMETRY_OPTOUT";
- private const string DotnetOptOut = "DOTNET_CLI_TELEMETRY_OPTOUT";
- private const string TelemetrySampleRateOverrideEnvVarName = "MSBUILD_TELEMETRY_SAMPLE_RATE";
- private const string VS1714TelemetryOptInEnvVarName = "MSBUILD_TELEMETRY_OPTIN";
-
- public OpenTelemetryManagerTests()
- {
- }
-
- public void Dispose()
- {
- ResetManagerState();
- }
-
- [Theory]
- [InlineData(DotnetOptOut, "true")]
- [InlineData(TelemetryFxOptoutEnvVarName, "true")]
- [InlineData(DotnetOptOut, "1")]
- [InlineData(TelemetryFxOptoutEnvVarName, "1")]
- public void Initialize_ShouldSetStateToOptOut_WhenOptOutEnvVarIsTrue(string optoutVar, string value)
- {
- // Arrange
- using TestEnvironment environment = TestEnvironment.Create();
- environment.SetEnvironmentVariable(optoutVar, value);
-
- // Act
- OpenTelemetryManager.Instance.Initialize(isStandalone: false);
-
- // Assert
- OpenTelemetryManager.Instance.IsActive().ShouldBeFalse();
- }
-
-#if NETCOREAPP
- [Fact]
- public void Initialize_ShouldSetStateToUnsampled_WhenNoOverrideOnNetCore()
- {
- using TestEnvironment environment = TestEnvironment.Create();
- environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, null);
- environment.SetEnvironmentVariable(DotnetOptOut, null);
-
- OpenTelemetryManager.Instance.Initialize(isStandalone: false);
-
- // If no override on .NET, we expect no Active ActivitySource
- OpenTelemetryManager.Instance.DefaultActivitySource.ShouldBeNull();
- }
-#endif
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- public void Initialize_ShouldSetSampleRateOverride_AndCreateActivitySource_WhenRandomBelowOverride(bool standalone)
- {
- // Arrange
- using TestEnvironment environment = TestEnvironment.Create();
- environment.SetEnvironmentVariable(VS1714TelemetryOptInEnvVarName, "1");
- environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, "1.0");
- environment.SetEnvironmentVariable(DotnetOptOut, null);
-
- // Act
- OpenTelemetryManager.Instance.Initialize(isStandalone: standalone);
-
- // Assert
- OpenTelemetryManager.Instance.IsActive().ShouldBeTrue();
- OpenTelemetryManager.Instance.DefaultActivitySource.ShouldNotBeNull();
- }
-
- [Fact]
- public void Initialize_ShouldNoOp_WhenCalledMultipleTimes()
- {
- using TestEnvironment environment = TestEnvironment.Create();
- environment.SetEnvironmentVariable(DotnetOptOut, "true");
- OpenTelemetryManager.Instance.Initialize(isStandalone: true);
- var state1 = OpenTelemetryManager.Instance.IsActive();
-
- environment.SetEnvironmentVariable(DotnetOptOut, null);
- OpenTelemetryManager.Instance.Initialize(isStandalone: true);
- var state2 = OpenTelemetryManager.Instance.IsActive();
-
- // Because the manager is already initialized, second call is a no-op
- state1.ShouldBe(false);
- state2.ShouldBe(false);
- }
-
- [Fact]
- public void TelemetryLoadFailureIsLoggedOnce()
- {
- OpenTelemetryManager.Instance.LoadFailureExceptionMessage = new System.IO.FileNotFoundException().ToString();
- using BuildManager bm = new BuildManager();
- var deferredMessages = new List();
- bm.BeginBuild(new BuildParameters(), deferredMessages);
- deferredMessages.ShouldContain(x => x.Text.Contains("FileNotFound"));
- bm.EndBuild();
- bm.BeginBuild(new BuildParameters());
- bm.EndBuild();
-
- // should not add message twice
- int count = deferredMessages.Count(x => x.Text.Contains("FileNotFound"));
- count.ShouldBe(1);
- }
-
- /* Helper methods */
-
- ///
- /// Resets the singleton manager to a known uninitialized state so each test is isolated.
- ///
- private void ResetManagerState()
- {
- var instance = OpenTelemetryManager.Instance;
-
- // 1. Reset the private _telemetryState field
- var telemetryStateField = typeof(OpenTelemetryManager)
- .GetField("_telemetryState", BindingFlags.NonPublic | BindingFlags.Instance);
- telemetryStateField?.SetValue(instance, OpenTelemetryManager.TelemetryState.Uninitialized);
-
- // 2. Null out the DefaultActivitySource property
- var defaultSourceProp = typeof(OpenTelemetryManager)
- .GetProperty(nameof(OpenTelemetryManager.DefaultActivitySource),
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
- defaultSourceProp?.SetValue(instance, null);
- }
- }
-}
diff --git a/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs b/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs
index f03ee221094..fb2459d683b 100644
--- a/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs
+++ b/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Text.Json;
+using System.Threading;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Telemetry;
@@ -14,10 +14,12 @@
using Shouldly;
using Xunit;
using Xunit.Abstractions;
+using static Microsoft.Build.Framework.Telemetry.BuildInsights;
+using static Microsoft.Build.Framework.Telemetry.TelemetryDataUtils;
namespace Microsoft.Build.Engine.UnitTests
{
- [Collection("OpenTelemetryManagerTests")]
+ [Collection("TelemetryManagerTests")]
public class Telemetry_Tests
{
private readonly ITestOutputHelper _output;
@@ -27,29 +29,6 @@ public Telemetry_Tests(ITestOutputHelper output)
_output = output;
}
- private sealed class ProjectFinishedCapturingLogger : ILogger
- {
- private readonly List _projectFinishedEventArgs = [];
- public LoggerVerbosity Verbosity { get; set; }
- public string? Parameters { get; set; }
-
- public IReadOnlyList ProjectFinishedEventArgsReceived =>
- _projectFinishedEventArgs;
-
- public void Initialize(IEventSource eventSource)
- {
- eventSource.ProjectFinished += EventSource_ProjectFinished;
- }
-
- private void EventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
- {
- _projectFinishedEventArgs.Add(e);
- }
-
- public void Shutdown()
- { }
- }
-
[Fact]
public void WorkerNodeTelemetryCollection_BasicTarget()
{
@@ -57,16 +36,16 @@ public void WorkerNodeTelemetryCollection_BasicTarget()
InternalTelemetryConsumingLogger.TestOnly_InternalTelemetryAggregted += dt => workerNodeTelemetryData = dt;
var testProject = """
-
-
-
-
-
-
-
-
-
- """;
+
+
+
+
+
+
+
+
+
+ """;
MockLogger logger = new MockLogger(_output);
Helpers.BuildProjectContentUsingBuildManager(testProject, logger,
@@ -79,9 +58,9 @@ public void WorkerNodeTelemetryCollection_BasicTarget()
workerNodeTelemetryData.TargetsExecutionData.Keys.Count.ShouldBe(1);
workerNodeTelemetryData.TasksExecutionData.Keys.Count.ShouldBeGreaterThan(2);
- ((int)workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].ExecutionsCount).ShouldBe(2);
+ workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].ExecutionsCount.ShouldBe(2);
workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
- ((int)workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].ExecutionsCount).ShouldBe(1);
+ workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].ExecutionsCount.ShouldBe(1);
workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
workerNodeTelemetryData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsCustom && !k.IsNuget);
@@ -92,8 +71,8 @@ public void WorkerNodeTelemetryCollection_BasicTarget()
[Fact]
public void WorkerNodeTelemetryCollection_CustomTargetsAndTasks()
{
- WorkerNodeTelemetryData? workerNodeTelemetryData = null;
- InternalTelemetryConsumingLogger.TestOnly_InternalTelemetryAggregted += dt => workerNodeTelemetryData = dt;
+ WorkerNodeTelemetryData? workerNodeData = null;
+ InternalTelemetryConsumingLogger.TestOnly_InternalTelemetryAggregted += dt => workerNodeData = dt;
var testProject = """
@@ -108,7 +87,6 @@ public void WorkerNodeTelemetryCollection_CustomTargetsAndTasks()
-
-
@@ -129,73 +106,77 @@ public void WorkerNodeTelemetryCollection_CustomTargetsAndTasks()
-
-
""";
+
MockLogger logger = new MockLogger(_output);
- Helpers.BuildProjectContentUsingBuildManager(testProject, logger,
+ Helpers.BuildProjectContentUsingBuildManager(
+ testProject,
+ logger,
new BuildParameters() { IsTelemetryEnabled = true }).OverallResult.ShouldBe(BuildResultCode.Success);
- workerNodeTelemetryData!.ShouldNotBeNull();
- workerNodeTelemetryData.TargetsExecutionData.ShouldContainKey(new TaskOrTargetTelemetryKey("Build", true, false));
- workerNodeTelemetryData.TargetsExecutionData[new TaskOrTargetTelemetryKey("Build", true, false)].ShouldBeTrue();
- workerNodeTelemetryData.TargetsExecutionData.ShouldContainKey(new TaskOrTargetTelemetryKey("BeforeBuild", true, false));
- workerNodeTelemetryData.TargetsExecutionData[new TaskOrTargetTelemetryKey("BeforeBuild", true, false)].ShouldBeTrue();
- workerNodeTelemetryData.TargetsExecutionData.ShouldContainKey(new TaskOrTargetTelemetryKey("NotExecuted", true, false));
- workerNodeTelemetryData.TargetsExecutionData[new TaskOrTargetTelemetryKey("NotExecuted", true, false)].ShouldBeFalse();
- workerNodeTelemetryData.TargetsExecutionData.Keys.Count.ShouldBe(3);
+ workerNodeData!.ShouldNotBeNull();
+ workerNodeData.TargetsExecutionData.ShouldContainKey(new TaskOrTargetTelemetryKey("Build", true, false));
+ workerNodeData.TargetsExecutionData[new TaskOrTargetTelemetryKey("Build", true, false)].ShouldBeTrue();
+ workerNodeData.TargetsExecutionData.ShouldContainKey(new TaskOrTargetTelemetryKey("BeforeBuild", true, false));
+ workerNodeData.TargetsExecutionData[new TaskOrTargetTelemetryKey("BeforeBuild", true, false)].ShouldBeTrue();
+ workerNodeData.TargetsExecutionData.ShouldContainKey(new TaskOrTargetTelemetryKey("NotExecuted", true, false));
+ workerNodeData.TargetsExecutionData[new TaskOrTargetTelemetryKey("NotExecuted", true, false)].ShouldBeFalse();
+ workerNodeData.TargetsExecutionData.Keys.Count.ShouldBe(3);
- workerNodeTelemetryData.TasksExecutionData.Keys.Count.ShouldBeGreaterThan(2);
- ((int)workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].ExecutionsCount).ShouldBe(3);
- workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
- ((int)workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].ExecutionsCount).ShouldBe(1);
- workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
+ workerNodeData.TasksExecutionData.Keys.Count.ShouldBeGreaterThan(2);
+ workerNodeData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].ExecutionsCount.ShouldBe(3);
+ workerNodeData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.Message"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
+ workerNodeData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].ExecutionsCount.ShouldBe(1);
+ workerNodeData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
- ((int)workerNodeTelemetryData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task01", true, false)].ExecutionsCount).ShouldBe(2);
- workerNodeTelemetryData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task01", true, false)].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
+ workerNodeData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task01", true, false)].ExecutionsCount.ShouldBe(2);
+ workerNodeData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task01", true, false)].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero);
- ((int)workerNodeTelemetryData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task02", true, false)].ExecutionsCount).ShouldBe(0);
- workerNodeTelemetryData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task02", true, false)].CumulativeExecutionTime.ShouldBe(TimeSpan.Zero);
+ workerNodeData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task02", true, false)].ExecutionsCount.ShouldBe(0);
+ workerNodeData.TasksExecutionData[new TaskOrTargetTelemetryKey("Task02", true, false)].CumulativeExecutionTime.ShouldBe(TimeSpan.Zero);
- workerNodeTelemetryData.TasksExecutionData.Values
- .Count(v => v.CumulativeExecutionTime > TimeSpan.Zero || v.ExecutionsCount > 0).ShouldBe(3);
+ workerNodeData.TasksExecutionData.Values.Count(v => v.CumulativeExecutionTime > TimeSpan.Zero || v.ExecutionsCount > 0).ShouldBe(3);
- workerNodeTelemetryData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsNuget);
+ workerNodeData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsNuget);
}
#if NET
- // test in .net core with opentelemetry opted in to avoid sending it but enable listening to it
+ // test in .net core with telemetry opted in to avoid sending it but enable listening to it
[Fact]
public void NodeTelemetryE2E()
{
using TestEnvironment env = TestEnvironment.Create();
- env.SetEnvironmentVariable("MSBUILD_TELEMETRY_OPTIN", "1");
- env.SetEnvironmentVariable("MSBUILD_TELEMETRY_SAMPLE_RATE", "1.0");
env.SetEnvironmentVariable("MSBUILD_TELEMETRY_OPTOUT", null);
env.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", null);
- // Reset the OpenTelemetryManager state to ensure clean test
- ResetManagerState();
-
- // track activities through an ActivityListener
var capturedActivities = new List();
+ using var activityStoppedEvent = new ManualResetEventSlim(false);
using var listener = new ActivityListener
{
ShouldListenTo = source => source.Name.StartsWith(TelemetryConstants.DefaultActivitySourceNamespace),
- Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData,
- ActivityStarted = capturedActivities.Add,
- ActivityStopped = _ => { }
+ Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded,
+ ActivityStarted = a => { lock (capturedActivities) { capturedActivities.Add(a); } },
+ ActivityStopped = a =>
+ {
+ if (a.DisplayName == "VS/MSBuild/Build")
+ {
+ activityStoppedEvent.Set();
+ }
+ },
};
ActivitySource.AddActivityListener(listener);
+ // Reset TelemetryManager to force re-initialization with our listener active
+ TelemetryManager.ResetForTest();
+
var testProject = @"
@@ -248,78 +229,76 @@ public void NodeTelemetryE2E()
// Phase 3: End Build - This puts telemetry to an system.diagnostics activity
buildManager.EndBuild();
-
- // Verify build activity were captured by the listener and contain task and target info
- capturedActivities.ShouldNotBeEmpty();
- var activity = capturedActivities.FindLast(a => a.DisplayName == "VS/MSBuild/Build").ShouldNotBeNull();
- var tags = activity.Tags.ToDictionary(t => t.Key, t => t.Value);
- tags.ShouldNotBeNull();
-
- tags.ShouldContainKey("VS.MSBuild.BuildTarget");
- tags["VS.MSBuild.BuildTarget"].ShouldNotBeNullOrEmpty();
-
- // Verify task data
- tags.ShouldContainKey("VS.MSBuild.Tasks");
- var tasksJson = tags["VS.MSBuild.Tasks"];
- tasksJson.ShouldNotBeNullOrEmpty();
- tasksJson.ShouldContain("Microsoft.Build.Tasks.Message");
- tasksJson.ShouldContain("Microsoft.Build.Tasks.CreateItem");
-
- // Parse tasks data for detailed assertions
- var tasksData = JsonSerializer.Deserialize(tasksJson);
-
- // Verify Message task execution metrics - updated for object structure
- tasksData.TryGetProperty("Microsoft.Build.Tasks.Message", out var messageTask).ShouldBe(true);
- messageTask.GetProperty("ExecutionsCount").GetInt32().ShouldBe(3);
- messageTask.GetProperty("TotalMilliseconds").GetDouble().ShouldBeGreaterThan(0);
- messageTask.GetProperty("TotalMemoryBytes").GetInt64().ShouldBeGreaterThanOrEqualTo(0);
- messageTask.GetProperty(nameof(TaskOrTargetTelemetryKey.IsCustom)).GetBoolean().ShouldBe(false);
- messageTask.GetProperty(nameof(TaskOrTargetTelemetryKey.IsCustom)).GetBoolean().ShouldBe(false);
-
- // Verify CreateItem task execution metrics - updated for object structure
- tasksData.TryGetProperty("Microsoft.Build.Tasks.CreateItem", out var createItemTask).ShouldBe(true);
- createItemTask.GetProperty("ExecutionsCount").GetInt32().ShouldBe(1);
- createItemTask.GetProperty("TotalMilliseconds").GetDouble().ShouldBeGreaterThan(0);
- createItemTask.GetProperty("TotalMemoryBytes").GetInt64().ShouldBeGreaterThanOrEqualTo(0);
-
- // Verify Targets summary information
- tags.ShouldContainKey("VS.MSBuild.TargetsSummary");
- var targetsSummaryJson = tags["VS.MSBuild.TargetsSummary"];
- targetsSummaryJson.ShouldNotBeNullOrEmpty();
- var targetsSummary = JsonSerializer.Deserialize(targetsSummaryJson);
-
- // Verify loaded and executed targets counts - match structure in TargetsSummaryConverter.Write
- targetsSummary.GetProperty("Loaded").GetProperty("Total").GetInt32().ShouldBe(2);
- targetsSummary.GetProperty("Executed").GetProperty("Total").GetInt32().ShouldBe(2);
-
- // Verify Tasks summary information
- tags.ShouldContainKey("VS.MSBuild.TasksSummary");
- var tasksSummaryJson = tags["VS.MSBuild.TasksSummary"];
- tasksSummaryJson.ShouldNotBeNullOrEmpty();
- var tasksSummary = JsonSerializer.Deserialize(tasksSummaryJson);
-
- // Verify task execution summary metrics based on TasksSummaryConverter.Write structure
- tasksSummary.GetProperty("Microsoft").GetProperty("Total").GetProperty("ExecutionsCount").GetInt32().ShouldBe(4);
- tasksSummary.GetProperty("Microsoft").GetProperty("Total").GetProperty("TotalMilliseconds").GetDouble().ShouldBeGreaterThan(0);
- // Allowing 0 for TotalMemoryBytes as it is possible for tasks to allocate no memory in certain scenarios.
- tasksSummary.GetProperty("Microsoft").GetProperty("Total").GetProperty("TotalMemoryBytes").GetInt64().ShouldBeGreaterThanOrEqualTo(0);
}
- // Reset the OpenTelemetryManager state to ensure it doesn't affect other tests
- ResetManagerState();
+
+ // Wait for the activity to be fully processed
+ activityStoppedEvent.Wait(TimeSpan.FromSeconds(10)).ShouldBeTrue("Timed out waiting for build activity to stop");
+
+ // Verify build activity were captured by the listener and contain task and target info
+ capturedActivities.ShouldNotBeEmpty();
+ var activity = capturedActivities.FindLast(a => a.DisplayName == "VS/MSBuild/Build").ShouldNotBeNull();
+ var tags = activity.Tags.ToDictionary(t => t.Key, t => t.Value);
+ tags.ShouldNotBeNull();
+
+ tags.ShouldContainKey("VS.MSBuild.BuildTarget");
+ tags["VS.MSBuild.BuildTarget"].ShouldNotBeNullOrEmpty();
+
+ // Verify task data
+ var tasks = activity.TagObjects.FirstOrDefault(to => to.Key == "VS.MSBuild.Tasks");
+
+ var tasksData = tasks.Value as List;
+ var messageTaskData = tasksData!.FirstOrDefault(t => t.Name == "Microsoft.Build.Tasks.Message");
+ messageTaskData.ShouldNotBeNull();
+
+ // Verify Message task execution metrics
+ messageTaskData.ExecutionsCount.ShouldBe(3);
+ messageTaskData.TotalMilliseconds.ShouldBeGreaterThan(0);
+ messageTaskData.TotalMemoryBytes.ShouldBeGreaterThanOrEqualTo(0);
+ messageTaskData.IsCustom.ShouldBe(false);
+
+ // Verify CreateItem task execution metrics
+ var createItemTaskData = tasksData!.FirstOrDefault(t => t.Name == "Microsoft.Build.Tasks.CreateItem");
+ createItemTaskData.ShouldNotBeNull();
+ createItemTaskData.ExecutionsCount.ShouldBe(1);
+ createItemTaskData.TotalMilliseconds.ShouldBeGreaterThan(0);
+ createItemTaskData.TotalMemoryBytes.ShouldBeGreaterThanOrEqualTo(0);
+
+ // Verify Targets summary information
+ var targetsSummaryTagObject = activity.TagObjects.FirstOrDefault(to => to.Key.Contains("VS.MSBuild.TargetsSummary"));
+ var targetsSummary = targetsSummaryTagObject.Value as TargetsSummaryInfo;
+ targetsSummary.ShouldNotBeNull();
+ targetsSummary.Loaded.Total.ShouldBe(2);
+ targetsSummary.Executed.Total.ShouldBe(2);
+
+ // Verify Tasks summary information
+ var tasksSummaryTagObject = activity.TagObjects.FirstOrDefault(to => to.Key.Contains("VS.MSBuild.TasksSummary"));
+ var tasksSummary = tasksSummaryTagObject.Value as TasksSummaryInfo;
+ tasksSummary.ShouldNotBeNull();
+
+ tasksSummary.Microsoft.ShouldNotBeNull();
+ tasksSummary.Microsoft!.Total!.ExecutionsCount.ShouldBe(4);
+ tasksSummary.Microsoft!.Total!.TotalMilliseconds.ShouldBeGreaterThan(0);
+
+ // Allowing 0 for TotalMemoryBytes as it is possible for tasks to allocate no memory in certain scenarios.
+ tasksSummary.Microsoft.Total.TotalMemoryBytes.ShouldBeGreaterThanOrEqualTo(0);
}
+#endif
- private void ResetManagerState()
+ private sealed class ProjectFinishedCapturingLogger : ILogger
{
- var instance = OpenTelemetryManager.Instance;
- typeof(OpenTelemetryManager)
- .GetField("_telemetryState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
- ?.SetValue(instance, OpenTelemetryManager.TelemetryState.Uninitialized);
-
- typeof(OpenTelemetryManager)
- .GetProperty("DefaultActivitySource",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
- ?.SetValue(instance, null);
+ private readonly List _projectFinishedEventArgs = [];
+
+ public LoggerVerbosity Verbosity { get; set; }
+
+ public string? Parameters { get; set; }
+
+ public IReadOnlyList ProjectFinishedEventArgsReceived => _projectFinishedEventArgs;
+
+ public void Initialize(IEventSource eventSource) => eventSource.ProjectFinished += EventSource_ProjectFinished;
+
+ private void EventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) => _projectFinishedEventArgs.Add(e);
+
+ public void Shutdown() { }
}
-#endif
}
}
diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs
index 7a70791f17b..887ce2d15ca 100644
--- a/src/Build/BackEnd/BuildManager/BuildManager.cs
+++ b/src/Build/BackEnd/BuildManager/BuildManager.cs
@@ -26,13 +26,13 @@
using Microsoft.Build.Exceptions;
using Microsoft.Build.Experimental.BuildCheck;
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
-using Microsoft.Build.ProjectCache;
using Microsoft.Build.FileAccesses;
using Microsoft.Build.Framework;
using Microsoft.Build.Framework.Telemetry;
using Microsoft.Build.Graph;
using Microsoft.Build.Internal;
using Microsoft.Build.Logging;
+using Microsoft.Build.ProjectCache;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.Debugging;
using Microsoft.Build.Shared.FileSystem;
@@ -280,7 +280,7 @@ public class BuildManager : INodePacketHandler, IBuildComponentHost, IDisposable
///
/// Creates a new unnamed build manager.
/// Normally there is only one build manager in a process, and it is the default build manager.
- /// Access it with
+ /// Access it with .
///
public BuildManager()
: this("Unnamed")
@@ -290,11 +290,12 @@ public BuildManager()
///
/// Creates a new build manager with an arbitrary distinct name.
/// Normally there is only one build manager in a process, and it is the default build manager.
- /// Access it with
+ /// Access it with .
///
public BuildManager(string hostName)
{
ErrorUtilities.VerifyThrowArgumentNull(hostName);
+
_hostName = hostName;
_buildManagerState = BuildManagerState.Idle;
_buildSubmissions = new Dictionary();
@@ -336,12 +337,12 @@ private enum BuildManagerState
///
/// This is the state the BuildManager is in after has been called but before has been called.
- /// , , , , and may be called in this state.
+ /// , , , , and may be called in this state.
///
Building,
///
- /// This is the state the BuildManager is in after has been called but before all existing submissions have completed.
+ /// This is the state the BuildManager is in after has been called but before all existing submissions have completed.
///
WaitingForBuildToComplete
}
@@ -459,8 +460,11 @@ private void UpdatePriority(Process p, ProcessPriorityClass priority)
/// Thrown if a build is already in progress.
public void BeginBuild(BuildParameters parameters)
{
- InitializeTelemetry();
-
+#if NETFRAMEWORK
+ // Collect telemetry unless explicitly opted out via environment variable.
+ // The decision to send telemetry is made at EndBuild to avoid eager loading of telemetry assemblies.
+ parameters.IsTelemetryEnabled |= !TelemetryManager.IsOptOut();
+#endif
if (_previousLowPriority != null)
{
if (parameters.LowPriority != _previousLowPriority)
@@ -529,6 +533,7 @@ public void BeginBuild(BuildParameters parameters)
}
_buildTelemetry.InnerStartAt = now;
+ _buildTelemetry.IsStandaloneExecution ??= false;
if (BuildParameters.DumpOpportunisticInternStats)
{
@@ -585,7 +590,6 @@ public void BeginBuild(BuildParameters parameters)
// Initialize components.
_nodeManager = ((IBuildComponentHost)this).GetComponent(BuildComponentType.NodeManager) as INodeManager;
- _buildParameters.IsTelemetryEnabled |= OpenTelemetryManager.Instance.IsActive();
var loggingService = InitializeLoggingService();
// Log deferred messages and response files
@@ -739,25 +743,6 @@ void InitializeCaches()
}
}
- private void InitializeTelemetry()
- {
- OpenTelemetryManager.Instance.Initialize(isStandalone: false);
- string? failureMessage = OpenTelemetryManager.Instance.LoadFailureExceptionMessage;
- if (_deferredBuildMessages != null &&
- failureMessage != null &&
- _deferredBuildMessages is ICollection deferredBuildMessagesCollection)
- {
- deferredBuildMessagesCollection.Add(
- new DeferredBuildMessage(
- ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword(
- "OpenTelemetryLoadFailed",
- failureMessage),
- MessageImportance.Low));
-
- // clean up the message from OpenTelemetryManager to avoid double logging it
- OpenTelemetryManager.Instance.LoadFailureExceptionMessage = null;
- }
- }
#if FEATURE_REPORTFILEACCESSES
///
@@ -1120,6 +1105,7 @@ public void EndBuild()
{
host = "VSCode";
}
+
_buildTelemetry.BuildEngineHost = host;
_buildTelemetry.BuildCheckEnabled = _buildParameters!.IsBuildCheckEnabled;
@@ -1129,10 +1115,8 @@ public void EndBuild()
_buildTelemetry.SACEnabled = sacState == NativeMethodsShared.SAC_State.Evaluation || sacState == NativeMethodsShared.SAC_State.Enforcement;
loggingService.LogTelemetry(buildEventContext: null, _buildTelemetry.EventName, _buildTelemetry.GetProperties());
- if (OpenTelemetryManager.Instance.IsActive())
- {
- EndBuildTelemetry();
- }
+
+ EndBuildTelemetry();
// Clean telemetry to make it ready for next build submission.
_buildTelemetry = null;
@@ -1176,18 +1160,18 @@ void SerializeCaches()
}
}
- [MethodImpl(MethodImplOptions.NoInlining)] // avoid assembly loads of System.Diagnostics.DiagnosticSource, TODO: when this is agreed to perf-wise enable instrumenting using activities anywhere...
+ [MethodImpl(MethodImplOptions.NoInlining)]
private void EndBuildTelemetry()
{
- OpenTelemetryManager.Instance.DefaultActivitySource?
- .StartActivity("Build")?
- .WithTags(_buildTelemetry)
- .WithTags(_telemetryConsumingLogger?.WorkerNodeTelemetryData.AsActivityDataHolder(
- includeTasksDetails: !Traits.Instance.ExcludeTasksDetailsFromTelemetry,
- includeTargetDetails: false))
- .WithStartTime(_buildTelemetry!.InnerStartAt)
- .Dispose();
- OpenTelemetryManager.Instance.ForceFlush();
+ TelemetryManager.Instance.Initialize(isStandalone: false);
+
+ using IActivity? activity = TelemetryManager.Instance
+ ?.DefaultActivitySource
+ ?.StartActivity(TelemetryConstants.Build)
+ ?.SetTags(_buildTelemetry)
+ ?.SetTags(_telemetryConsumingLogger?.WorkerNodeTelemetryData.AsActivityDataHolder(
+ includeTasksDetails: !Traits.Instance.ExcludeTasksDetailsFromTelemetry,
+ includeTargetDetails: false));
}
///
@@ -3040,8 +3024,7 @@ private ILoggingService CreateLoggingService(
loggerSwitchParameters: null,
verbosity: LoggerVerbosity.Quiet);
- _telemetryConsumingLogger =
- new InternalTelemetryConsumingLogger();
+ _telemetryConsumingLogger = new InternalTelemetryConsumingLogger();
ForwardingLoggerRecord[] forwardingLogger = { new ForwardingLoggerRecord(_telemetryConsumingLogger, forwardingLoggerDescription) };
@@ -3053,7 +3036,6 @@ private ILoggingService CreateLoggingService(
loggingService.EnableTargetOutputLogging = true;
}
-
try
{
if (loggers != null)
@@ -3265,6 +3247,8 @@ private void Dispose(bool disposing)
s_singletonInstance = null;
}
+ TelemetryManager.Instance?.Dispose();
+
_disposed = true;
}
}
diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs
index 487c10b69b0..d2067256d9c 100644
--- a/src/Build/BackEnd/Components/Logging/LoggingService.cs
+++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs
@@ -1848,6 +1848,11 @@ private void UpdateMinimumMessageImportance(ILogger logger)
// The null logger has no effect on minimum verbosity.
Execution.BuildManager.NullLogger => null,
+ // Telemetry loggers only consume WorkerNodeTelemetryLogged events, not message events.
+ // They have no effect on minimum message verbosity.
+ TelemetryInfra.InternalTelemetryConsumingLogger => null,
+ Framework.Telemetry.InternalTelemetryForwardingLogger => null,
+
TerminalLogger terminalLogger => terminalLogger.GetMinimumMessageImportance(),
_ =>
innerLogger.GetType().FullName == "Microsoft.Build.Logging.TerminalLogger"
diff --git a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs
index d99df97edb7..f4eeca2c1ef 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/RequestBuilder.cs
@@ -1267,9 +1267,9 @@ private void UpdateStatisticsPostBuild()
{
ITelemetryForwarder telemetryForwarder =
((TelemetryForwarderProvider)_componentHost.GetComponent(BuildComponentType.TelemetryForwarder))
- .Instance;
+ ?.Instance;
- if (!telemetryForwarder.IsTelemetryCollected)
+ if (telemetryForwarder == null || !telemetryForwarder.IsTelemetryCollected)
{
return;
}
@@ -1279,6 +1279,11 @@ private void UpdateStatisticsPostBuild()
// Hence we need to fetch the original result from the cache - to get the data for all executed targets.
BuildResult unfilteredResult = resultsCache.GetResultsForConfiguration(_requestEntry.Request.ConfigurationId);
+ if (unfilteredResult?.ResultsByTarget == null || _requestEntry.RequestConfiguration.Project?.Targets == null)
+ {
+ return;
+ }
+
foreach (var projectTargetInstance in _requestEntry.RequestConfiguration.Project.Targets)
{
bool wasExecuted =
diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index d53ecf9d743..a0834749ca8 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -182,7 +182,6 @@
-
diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx
index 68b59de6ee8..a06d23c2862 100644
--- a/src/Build/Resources/Strings.resx
+++ b/src/Build/Resources/Strings.resx
@@ -2430,9 +2430,6 @@ Utilization: {0} Average Utilization: {1:###.0}
succeeded: {0}{0} whole number
-
- Loading telemetry libraries failed with exception: {0}.
-
Custom TaskFactory '{0}' for Task '{1}' does not support out of process TaskHost execution. Turn off the multithreaded build mode or remove the custom TaskFactory from your <UsingTask> definitions in project files.
diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf
index 86d6fc9c60c..59ae407f8fb 100644
--- a/src/Build/Resources/xlf/Strings.cs.xlf
+++ b/src/Build/Resources/xlf/Strings.cs.xlf
@@ -652,11 +652,6 @@
Metoda {0} se nedá zavolat s kolekcí, která obsahuje prázdné cílové názvy nebo názvy null.
-
- Loading telemetry libraries failed with exception: {0}.
- Načítání knihoven telemetrie se nezdařilo s výjimkou: {0}.
-
- Output Property: Výstupní vlastnost:
diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf
index 4adaf0b3973..a7d1c190ecb 100644
--- a/src/Build/Resources/xlf/Strings.de.xlf
+++ b/src/Build/Resources/xlf/Strings.de.xlf
@@ -652,11 +652,6 @@
Die Methode "{0}" kann nicht mit einer Sammlung aufgerufen werden, die NULL oder leere Zielnamen enthält.
-
- Loading telemetry libraries failed with exception: {0}.
- Fehler beim Laden von Telemetriebibliotheken. Ausnahme:{0}.
-
- Output Property: Ausgabeeigenschaft:
diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf
index ed841430d86..678f2048e28 100644
--- a/src/Build/Resources/xlf/Strings.es.xlf
+++ b/src/Build/Resources/xlf/Strings.es.xlf
@@ -652,11 +652,6 @@
No se puede llamar al método {0} con una colección que contiene nombres de destino nulos o vacíos.
-
- Loading telemetry libraries failed with exception: {0}.
- Error al cargar las bibliotecas de telemetría con la excepción: {0}.
-
- Output Property: Propiedad de salida:
diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf
index 2a866b00d4e..803a76c23fc 100644
--- a/src/Build/Resources/xlf/Strings.fr.xlf
+++ b/src/Build/Resources/xlf/Strings.fr.xlf
@@ -652,11 +652,6 @@
Impossible d'appeler la méthode {0} avec une collection contenant des noms de cibles qui ont une valeur null ou qui sont vides.
-
- Loading telemetry libraries failed with exception: {0}.
- Nous n’avons pas pu charger les bibliothèques de télémétrie avec l’exception : {0}.
-
- Output Property: Propriété de sortie :
diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf
index 99d4affd579..19bd59f4704 100644
--- a/src/Build/Resources/xlf/Strings.it.xlf
+++ b/src/Build/Resources/xlf/Strings.it.xlf
@@ -652,11 +652,6 @@
Non è possibile chiamare il metodo {0} con una raccolta contenente nomi di destinazione Null o vuoti.
-
- Loading telemetry libraries failed with exception: {0}.
- Caricamento delle librerie di telemetria non riuscito con eccezione: {0}.
-
- Output Property: Proprietà di output:
diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf
index 38b60d181d3..89b779c7e33 100644
--- a/src/Build/Resources/xlf/Strings.ja.xlf
+++ b/src/Build/Resources/xlf/Strings.ja.xlf
@@ -652,11 +652,6 @@
Null または空のターゲット名を含むコレクションを指定してメソッド {0} を呼び出すことはできません。
-
- Loading telemetry libraries failed with exception: {0}.
- テレメトリ ライブラリの読み込みが次の例外で失敗しました: {0}。
-
- Output Property: プロパティの出力:
diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf
index e5aa1a02533..416c24981dd 100644
--- a/src/Build/Resources/xlf/Strings.ko.xlf
+++ b/src/Build/Resources/xlf/Strings.ko.xlf
@@ -652,11 +652,6 @@
null 또는 빈 대상 이름을 포함하는 컬렉션을 사용하여 {0} 메서드를 호출할 수 없습니다.
-
- Loading telemetry libraries failed with exception: {0}.
- 예외 {0}(으)로 인해 원격 분석 라이브러리를 로드하지 못했습니다.
-
- Output Property: 출력 속성:
diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf
index 866d90bcd9d..3619a85bb47 100644
--- a/src/Build/Resources/xlf/Strings.pl.xlf
+++ b/src/Build/Resources/xlf/Strings.pl.xlf
@@ -652,11 +652,6 @@
Metody {0} nie można wywołać przy użyciu kolekcji zawierającej nazwy docelowe o wartości null lub puste.
-
- Loading telemetry libraries failed with exception: {0}.
- Ładowanie bibliotek telemetrii nie powiodło się. Wyjątek: {0}.
-
- Output Property: Właściwość danych wyjściowych:
diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf
index d27c50e5464..4a9ea5a9276 100644
--- a/src/Build/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf
@@ -652,11 +652,6 @@
O método {0} não pode ser chamado com uma coleção que contém nomes de destino nulos ou vazios.
-
- Loading telemetry libraries failed with exception: {0}.
- Falha ao carregar as bibliotecas de telemetria com a exceção: {0}.
-
- Output Property: Propriedade de Saída:
diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf
index 55d613e49cd..54fd0f966b5 100644
--- a/src/Build/Resources/xlf/Strings.ru.xlf
+++ b/src/Build/Resources/xlf/Strings.ru.xlf
@@ -652,11 +652,6 @@
Метод {0} не может быть вызван с коллекцией, содержащей целевые имена, которые пусты или равны NULL.
-
- Loading telemetry libraries failed with exception: {0}.
- Не удалось загрузить библиотеки телеметрии с исключением: {0}.
-
- Output Property: Выходное свойство:
diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf
index 7a5e481602b..5881a1e73f3 100644
--- a/src/Build/Resources/xlf/Strings.tr.xlf
+++ b/src/Build/Resources/xlf/Strings.tr.xlf
@@ -652,11 +652,6 @@
{0} metosu null veya boş hedef adları içeren bir koleksiyonla çağrılamaz.
-
- Loading telemetry libraries failed with exception: {0}.
- Telemetri kitaplıklarının yüklenmesi şu hayatla başarısız oldu: {0}.
-
- Output Property: Çıkış Özelliği:
diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
index d8da78803bc..84251ce69b5 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
@@ -652,11 +652,6 @@
无法使用包含 null 或空目标名称的集合调用方法 {0}。
-
- Loading telemetry libraries failed with exception: {0}.
- 加载遥测库失败,出现异常: {0}。
-
- Output Property: 输出属性:
diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
index d851b21ba23..2c44f0635da 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
@@ -652,11 +652,6 @@
無法使用內含 null 或空白目標名稱的集合呼叫方法 {0}。
-
- Loading telemetry libraries failed with exception: {0}.
- 載入遙測程式庫時發生例外狀況: {0}。
-
- Output Property: 輸出屬性:
diff --git a/src/Build/TelemetryInfra/ITelemetryForwarder.cs b/src/Build/TelemetryInfra/ITelemetryForwarder.cs
index 15d021bfb81..97735076593 100644
--- a/src/Build/TelemetryInfra/ITelemetryForwarder.cs
+++ b/src/Build/TelemetryInfra/ITelemetryForwarder.cs
@@ -14,7 +14,12 @@ internal interface ITelemetryForwarder
{
bool IsTelemetryCollected { get; }
- void AddTask(string name, TimeSpan cumulativeExecutionTime, short executionsCount, long totalMemoryConsumed, bool isCustom,
+ void AddTask(
+ string name,
+ TimeSpan cumulativeExecutionTime,
+ short executionsCount,
+ long totalMemoryConsumed,
+ bool isCustom,
bool isFromNugetCache);
///
diff --git a/src/Build/TelemetryInfra/InternalTelemetryConsumingLogger.cs b/src/Build/TelemetryInfra/InternalTelemetryConsumingLogger.cs
index b028dd4b7fa..d4a388d79ce 100644
--- a/src/Build/TelemetryInfra/InternalTelemetryConsumingLogger.cs
+++ b/src/Build/TelemetryInfra/InternalTelemetryConsumingLogger.cs
@@ -11,7 +11,9 @@ namespace Microsoft.Build.TelemetryInfra;
internal sealed class InternalTelemetryConsumingLogger : ILogger
{
public LoggerVerbosity Verbosity { get; set; }
+
public string? Parameters { get; set; }
+
internal static event Action? TestOnly_InternalTelemetryAggregted;
public void Initialize(IEventSource eventSource)
@@ -70,12 +72,14 @@ private void FlushDataIntoConsoleIfRequested()
{
Console.WriteLine($"{task.Key} - {task.Value.TotalMemoryBytes / 1024.0:0.00}kB");
}
+
Console.WriteLine("==========================================");
Console.WriteLine("Tasks by Executions count:");
foreach (var task in _workerNodeTelemetryData.TasksExecutionData.OrderByDescending(t => t.Value.ExecutionsCount))
{
Console.WriteLine($"{task.Key} - {task.Value.ExecutionsCount}");
}
+
Console.WriteLine("==========================================");
}
diff --git a/src/Build/TelemetryInfra/TelemetryDataUtils.cs b/src/Build/TelemetryInfra/TelemetryDataUtils.cs
deleted file mode 100644
index e2759bec030..00000000000
--- a/src/Build/TelemetryInfra/TelemetryDataUtils.cs
+++ /dev/null
@@ -1,339 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace Microsoft.Build.Framework.Telemetry
-{
- internal static class TelemetryDataUtils
- {
- ///
- /// Transforms collected telemetry data to format recognized by the telemetry infrastructure.
- ///
- /// Data about tasks and target forwarded from nodes.
- /// Controls whether Task details should attached to the telemetry.
- /// Controls whether Target details should be attached to the telemetry.
- /// Node Telemetry data wrapped in a list of properties that can be attached as tags to a .
- public static IActivityTelemetryDataHolder? AsActivityDataHolder(this IWorkerNodeTelemetryData? telemetryData, bool includeTasksDetails, bool includeTargetDetails)
- {
- if (telemetryData == null)
- {
- return null;
- }
-
- List telemetryItems = new(4);
-
- if (includeTasksDetails)
- {
- telemetryItems.Add(new TelemetryItem(NodeTelemetryTags.Tasks,
- JsonSerializer.Serialize(telemetryData.TasksExecutionData, _serializerOptions), false));
- }
-
- if (includeTargetDetails)
- {
- telemetryItems.Add(new TelemetryItem(NodeTelemetryTags.Targets,
- JsonSerializer.Serialize(telemetryData.TargetsExecutionData, _serializerOptions), false));
- }
-
- TargetsSummaryConverter targetsSummary = new();
- targetsSummary.Process(telemetryData.TargetsExecutionData);
- telemetryItems.Add(new TelemetryItem(NodeTelemetryTags.TargetsSummary,
- JsonSerializer.Serialize(targetsSummary, _serializerOptions), false));
-
- TasksSummaryConverter tasksSummary = new();
- tasksSummary.Process(telemetryData.TasksExecutionData);
- telemetryItems.Add(new TelemetryItem(NodeTelemetryTags.TasksSummary,
- JsonSerializer.Serialize(tasksSummary, _serializerOptions), false));
-
- return new NodeTelemetry(telemetryItems);
- }
-
- private static JsonSerializerOptions _serializerOptions = CreateSerializerOptions();
-
- private static JsonSerializerOptions CreateSerializerOptions()
- {
- var opt = new JsonSerializerOptions
- {
- Converters =
- {
- new TargetsDetailsConverter(),
- new TasksDetailsConverter(),
- new TargetsSummaryConverter(),
- new TasksSummaryConverter(),
- },
- };
-
- return opt;
- }
-
- private class TargetsDetailsConverter : JsonConverter?>
- {
- public override Dictionary? Read(
- ref Utf8JsonReader reader,
- Type typeToConvert,
- JsonSerializerOptions options)
- =>
- throw new NotImplementedException("Reading is not supported");
-
- public override void Write(
- Utf8JsonWriter writer,
- Dictionary? value,
- JsonSerializerOptions options)
- {
- if (value == null)
- {
- throw new NotSupportedException("TaskOrTargetTelemetryKey cannot be null in telemetry data");
- }
-
- // Following needed - as System.Text.Json doesn't support indexing dictionary by composite types
- writer.WriteStartObject();
-
- foreach (KeyValuePair valuePair in value)
- {
- string keyName = ShouldHashKey(valuePair.Key) ?
- ActivityExtensions.GetHashed(valuePair.Key.Name) :
- valuePair.Key.Name;
-
- writer.WriteStartObject(keyName);
- writer.WriteBoolean("WasExecuted", valuePair.Value);
- writer.WriteBoolean(nameof(valuePair.Key.IsCustom), valuePair.Key.IsCustom);
- writer.WriteBoolean(nameof(valuePair.Key.IsNuget), valuePair.Key.IsNuget);
- writer.WriteBoolean(nameof(valuePair.Key.IsMetaProj), valuePair.Key.IsMetaProj);
- writer.WriteEndObject();
- }
-
- writer.WriteEndObject();
- }
-
- private bool ShouldHashKey(TaskOrTargetTelemetryKey key) => key.IsCustom || key.IsMetaProj;
- }
-
- private class TasksDetailsConverter : JsonConverter?>
- {
- public override Dictionary? Read(
- ref Utf8JsonReader reader,
- Type typeToConvert,
- JsonSerializerOptions options)
- =>
- throw new NotImplementedException("Reading is not supported");
-
- public override void Write(
- Utf8JsonWriter writer,
- Dictionary? value,
- JsonSerializerOptions options)
- {
- if (value == null)
- {
- throw new NotSupportedException("TaskOrTargetTelemetryKey cannot be null in telemetry data");
- }
-
- // Following needed - as System.Text.Json doesn't support indexing dictionary by composite types
- writer.WriteStartObject();
-
- foreach (KeyValuePair valuePair in value)
- {
- string keyName = valuePair.Key.IsCustom ?
- ActivityExtensions.GetHashed(valuePair.Key.Name) :
- valuePair.Key.Name;
- writer.WriteStartObject(keyName);
- writer.WriteNumber(nameof(valuePair.Value.CumulativeExecutionTime.TotalMilliseconds), valuePair.Value.CumulativeExecutionTime.TotalMilliseconds);
- writer.WriteNumber(nameof(valuePair.Value.ExecutionsCount), valuePair.Value.ExecutionsCount);
- writer.WriteNumber(nameof(valuePair.Value.TotalMemoryBytes), valuePair.Value.TotalMemoryBytes);
- writer.WriteBoolean(nameof(valuePair.Key.IsCustom), valuePair.Key.IsCustom);
- writer.WriteBoolean(nameof(valuePair.Key.IsNuget), valuePair.Key.IsNuget);
- writer.WriteEndObject();
- }
-
- writer.WriteEndObject();
- }
- }
-
- private class TargetsSummaryConverter : JsonConverter
- {
- ///
- /// Processes target execution data to compile summary statistics for both built-in and custom targets.
- ///
- /// Dictionary containing target execution data keyed by task identifiers.
- public void Process(Dictionary targetsExecutionData)
- {
- foreach (KeyValuePair targetPair in targetsExecutionData)
- {
- TaskOrTargetTelemetryKey key = targetPair.Key;
- bool wasExecuted = targetPair.Value;
-
- // Update loaded targets statistics (all targets are loaded)
- UpdateTargetStatistics(key, isExecuted: false);
-
- // Update executed targets statistics (only targets that were actually executed)
- if (wasExecuted)
- {
- UpdateTargetStatistics(key, isExecuted: true);
- }
- }
- }
-
- private void UpdateTargetStatistics(TaskOrTargetTelemetryKey key, bool isExecuted)
- {
- // Select the appropriate target info collections based on execution state
- TargetInfo builtinTargetInfo = isExecuted ? ExecutedBuiltinTargetInfo : LoadedBuiltinTargetInfo;
- TargetInfo customTargetInfo = isExecuted ? ExecutedCustomTargetInfo : LoadedCustomTargetInfo;
-
- // Update either custom or builtin target info based on target type
- TargetInfo targetInfo = key.IsCustom ? customTargetInfo : builtinTargetInfo;
-
- targetInfo.Total++;
- if (key.IsNuget)
- {
- targetInfo.FromNuget++;
- }
- if (key.IsMetaProj)
- {
- targetInfo.FromMetaproj++;
- }
- }
-
- private TargetInfo LoadedBuiltinTargetInfo { get; } = new();
- private TargetInfo LoadedCustomTargetInfo { get; } = new();
- private TargetInfo ExecutedBuiltinTargetInfo { get; } = new();
- private TargetInfo ExecutedCustomTargetInfo { get; } = new();
-
- private class TargetInfo
- {
- public int Total { get; internal set; }
- public int FromNuget { get; internal set; }
- public int FromMetaproj { get; internal set; }
- }
-
- public override TargetsSummaryConverter? Read(
- ref Utf8JsonReader reader,
- Type typeToConvert,
- JsonSerializerOptions options) =>
- throw new NotImplementedException("Reading is not supported");
-
- public override void Write(
- Utf8JsonWriter writer,
- TargetsSummaryConverter value,
- JsonSerializerOptions options)
- {
- writer.WriteStartObject();
- writer.WriteStartObject("Loaded");
- WriteStat(writer, value.LoadedBuiltinTargetInfo, value.LoadedCustomTargetInfo);
- writer.WriteEndObject();
- writer.WriteStartObject("Executed");
- WriteStat(writer, value.ExecutedBuiltinTargetInfo, value.ExecutedCustomTargetInfo);
- writer.WriteEndObject();
- writer.WriteEndObject();
-
- void WriteStat(Utf8JsonWriter writer, TargetInfo builtinTargetsInfo, TargetInfo customTargetsInfo)
- {
- writer.WriteNumber(nameof(builtinTargetsInfo.Total), builtinTargetsInfo.Total + customTargetsInfo.Total);
- WriteSingleStat(writer, builtinTargetsInfo, "Microsoft");
- WriteSingleStat(writer, customTargetsInfo, "Custom");
- }
-
- void WriteSingleStat(Utf8JsonWriter writer, TargetInfo targetInfo, string name)
- {
- if (targetInfo.Total > 0)
- {
- writer.WriteStartObject(name);
- writer.WriteNumber(nameof(targetInfo.Total), targetInfo.Total);
- writer.WriteNumber(nameof(targetInfo.FromNuget), targetInfo.FromNuget);
- writer.WriteNumber(nameof(targetInfo.FromMetaproj), targetInfo.FromMetaproj);
- writer.WriteEndObject();
- }
- }
- }
- }
-
- private class TasksSummaryConverter : JsonConverter
- {
- ///
- /// Processes task execution data to compile summary statistics for both built-in and custom tasks.
- ///
- /// Dictionary containing task execution data keyed by task identifiers.
- public void Process(Dictionary tasksExecutionData)
- {
- foreach (KeyValuePair taskInfo in tasksExecutionData)
- {
- UpdateTaskStatistics(BuiltinTasksInfo, CustomTasksInfo, taskInfo.Key, taskInfo.Value);
- }
- }
-
- private void UpdateTaskStatistics(
- TasksInfo builtinTaskInfo,
- TasksInfo customTaskInfo,
- TaskOrTargetTelemetryKey key,
- TaskExecutionStats taskExecutionStats)
- {
- TasksInfo taskInfo = key.IsCustom ? customTaskInfo : builtinTaskInfo;
- taskInfo.Total.Accumulate(taskExecutionStats);
-
- if (key.IsNuget)
- {
- taskInfo.FromNuget.Accumulate(taskExecutionStats);
- }
- }
-
- private TasksInfo BuiltinTasksInfo { get; } = new TasksInfo();
-
- private TasksInfo CustomTasksInfo { get; } = new TasksInfo();
-
- private class TasksInfo
- {
- public TaskExecutionStats Total { get; } = TaskExecutionStats.CreateEmpty();
-
- public TaskExecutionStats FromNuget { get; } = TaskExecutionStats.CreateEmpty();
- }
-
- public override TasksSummaryConverter? Read(
- ref Utf8JsonReader reader,
- Type typeToConvert,
- JsonSerializerOptions options) =>
- throw new NotImplementedException("Reading is not supported");
-
- public override void Write(
- Utf8JsonWriter writer,
- TasksSummaryConverter value,
- JsonSerializerOptions options)
- {
- writer.WriteStartObject();
- WriteStat(writer, value.BuiltinTasksInfo, "Microsoft");
- WriteStat(writer, value.CustomTasksInfo, "Custom");
- writer.WriteEndObject();
-
- void WriteStat(Utf8JsonWriter writer, TasksInfo tasksInfo, string name)
- {
- writer.WriteStartObject(name);
- WriteSingleStat(writer, tasksInfo.Total, nameof(tasksInfo.Total));
- WriteSingleStat(writer, tasksInfo.FromNuget, nameof(tasksInfo.FromNuget));
- writer.WriteEndObject();
- }
-
- void WriteSingleStat(Utf8JsonWriter writer, TaskExecutionStats stats, string name)
- {
- if (stats.ExecutionsCount > 0)
- {
- writer.WriteStartObject(name);
- writer.WriteNumber(nameof(stats.ExecutionsCount), stats.ExecutionsCount);
- writer.WriteNumber(nameof(stats.CumulativeExecutionTime.TotalMilliseconds), stats.CumulativeExecutionTime.TotalMilliseconds);
- writer.WriteNumber(nameof(stats.TotalMemoryBytes), stats.TotalMemoryBytes);
- writer.WriteEndObject();
- }
- }
- }
- }
-
- private class NodeTelemetry : IActivityTelemetryDataHolder
- {
- private readonly IList _items;
-
- public NodeTelemetry(IList items) => _items = items;
-
- public IList GetActivityProperties()
- => _items;
- }
- }
-}
diff --git a/src/Framework/Microsoft.Build.Framework.csproj b/src/Framework/Microsoft.Build.Framework.csproj
index 2f972d7903e..2820f9af784 100644
--- a/src/Framework/Microsoft.Build.Framework.csproj
+++ b/src/Framework/Microsoft.Build.Framework.csproj
@@ -24,21 +24,19 @@
-
-
-
+
+
-
+
-
diff --git a/src/Framework/Telemetry/ActivityExtensions.cs b/src/Framework/Telemetry/ActivityExtensions.cs
deleted file mode 100644
index 9b4e05f7c02..00000000000
--- a/src/Framework/Telemetry/ActivityExtensions.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace Microsoft.Build.Framework.Telemetry
-{
- ///
- /// Extension methods for . usage in VS OpenTelemetry.
- ///
- internal static class ActivityExtensions
- {
- ///
- /// Add tags to the activity from a .
- ///
- public static Activity WithTags(this Activity activity, IActivityTelemetryDataHolder? dataHolder)
- {
- if (dataHolder != null)
- {
- activity.WithTags(dataHolder.GetActivityProperties());
- }
- return activity;
- }
-
- ///
- /// Add tags to the activity from a list of TelemetryItems.
- ///
- public static Activity WithTags(this Activity activity, IList tags)
- {
- foreach (var tag in tags)
- {
- activity.WithTag(tag);
- }
- return activity;
- }
- ///
- /// Add a tag to the activity from a .
- ///
- public static Activity WithTag(this Activity activity, TelemetryItem item)
- {
- object value = item.NeedsHashing ? GetHashed(item.Value) : item.Value;
- activity.SetTag($"{TelemetryConstants.PropertyPrefix}{item.Name}", value);
- return activity;
- }
-
- ///
- /// Set the start time of the activity.
- ///
- public static Activity WithStartTime(this Activity activity, DateTime? startTime)
- {
- if (startTime.HasValue)
- {
- activity.SetStartTime(startTime.Value);
- }
- return activity;
- }
-
- ///
- /// Depending on the platform, hash the value using an available mechanism.
- ///
- internal static string GetHashed(object value)
- {
- return Sha256Hasher.Hash(value.ToString() ?? "");
- }
-
- // https://github.com/dotnet/sdk/blob/8bd19a2390a6bba4aa80d1ac3b6c5385527cc311/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs + workaround for netstandard2.0
- private static class Sha256Hasher
- {
- ///
- /// The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode)
- ///
- public static string Hash(string text)
- {
- byte[] bytes = Encoding.UTF8.GetBytes(text);
-#if NET
- byte[] hash = SHA256.HashData(bytes);
-#if NET9_0_OR_GREATER
- return Convert.ToHexStringLower(hash);
-#else
- return Convert.ToHexString(hash).ToLowerInvariant();
-#endif
-
-#else
- // Create the SHA256 object and compute the hash
- using (var sha256 = SHA256.Create())
- {
- byte[] hash = sha256.ComputeHash(bytes);
-
- // Convert the hash bytes to a lowercase hex string (manual loop approach)
- var sb = new StringBuilder(hash.Length * 2);
- foreach (byte b in hash)
- {
- sb.AppendFormat("{0:x2}", b);
- }
-
- return sb.ToString();
- }
-#endif
- }
-
- public static string HashWithNormalizedCasing(string text)
- {
- return Hash(text.ToUpperInvariant());
- }
- }
- }
-}
diff --git a/src/Framework/Telemetry/BuildCheckTelemetry.cs b/src/Framework/Telemetry/BuildCheckTelemetry.cs
index 3b8507203c1..8555a1b33e8 100644
--- a/src/Framework/Telemetry/BuildCheckTelemetry.cs
+++ b/src/Framework/Telemetry/BuildCheckTelemetry.cs
@@ -87,10 +87,7 @@ internal class BuildCheckTelemetry
yield return (RuleStatsEventName, properties);
}
-
// set for the new submission in case of build server
_submissionId = Guid.NewGuid();
}
}
-
-
diff --git a/src/Framework/Telemetry/BuildInsights.cs b/src/Framework/Telemetry/BuildInsights.cs
new file mode 100644
index 00000000000..50858c09323
--- /dev/null
+++ b/src/Framework/Telemetry/BuildInsights.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using static Microsoft.Build.Framework.Telemetry.TelemetryDataUtils;
+
+namespace Microsoft.Build.Framework.Telemetry;
+
+///
+/// Container for all build telemetry insights including tasks and targets details and summaries.
+///
+internal sealed class BuildInsights
+{
+ public List Tasks { get; }
+
+ public List Targets { get; }
+
+ public TargetsSummaryInfo TargetsSummary { get; }
+
+ public TasksSummaryInfo TasksSummary { get; }
+
+ public BuildInsights(
+ List tasks,
+ List targets,
+ TargetsSummaryInfo targetsSummary,
+ TasksSummaryInfo tasksSummary)
+ {
+ Tasks = tasks;
+ Targets = targets;
+ TargetsSummary = targetsSummary;
+ TasksSummary = tasksSummary;
+ }
+
+ internal record TasksSummaryInfo(TaskCategoryStats? Microsoft, TaskCategoryStats? Custom);
+
+ internal record TaskCategoryStats(TaskStatsInfo? Total, TaskStatsInfo? FromNuget);
+
+ internal record TaskStatsInfo(int ExecutionsCount, double TotalMilliseconds, long TotalMemoryBytes);
+}
diff --git a/src/Framework/Telemetry/BuildTelemetry.cs b/src/Framework/Telemetry/BuildTelemetry.cs
index c20c5817558..acaf6033f97 100644
--- a/src/Framework/Telemetry/BuildTelemetry.cs
+++ b/src/Framework/Telemetry/BuildTelemetry.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Runtime.CompilerServices;
namespace Microsoft.Build.Framework.Telemetry
{
@@ -32,6 +33,11 @@ internal class BuildTelemetry : TelemetryBase, IActivityTelemetryDataHolder
///
public DateTime? InnerStartAt { get; set; }
+ ///
+ /// True if MSBuild runs from command line.
+ ///
+ public bool? IsStandaloneExecution { get; set; }
+
///
/// Time at which build have finished.
///
@@ -100,138 +106,83 @@ internal class BuildTelemetry : TelemetryBase, IActivityTelemetryDataHolder
///
public string? BuildEngineFrameworkName { get; set; }
- public override IDictionary GetProperties()
+ ///
+ /// Create a list of properties sent to VS telemetry.
+ ///
+ public Dictionary GetActivityProperties()
{
- var properties = new Dictionary();
-
- // populate property values
- if (BuildEngineDisplayVersion != null)
- {
- properties[nameof(BuildEngineDisplayVersion)] = BuildEngineDisplayVersion;
- }
+ Dictionary telemetryItems = new(8);
if (StartAt.HasValue && FinishedAt.HasValue)
{
- properties[TelemetryConstants.BuildDurationPropertyName] = (FinishedAt.Value - StartAt.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
+ telemetryItems.Add(TelemetryConstants.BuildDurationPropertyName, (FinishedAt.Value - StartAt.Value).TotalMilliseconds);
}
if (InnerStartAt.HasValue && FinishedAt.HasValue)
{
- properties[TelemetryConstants.InnerBuildDurationPropertyName] = (FinishedAt.Value - InnerStartAt.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
- }
-
- if (BuildEngineFrameworkName != null)
- {
- properties[nameof(BuildEngineFrameworkName)] = BuildEngineFrameworkName;
+ telemetryItems.Add(TelemetryConstants.InnerBuildDurationPropertyName, (FinishedAt.Value - InnerStartAt.Value).TotalMilliseconds);
}
- if (BuildEngineHost != null)
- {
- properties[nameof(BuildEngineHost)] = BuildEngineHost;
- }
-
- if (InitialMSBuildServerState != null)
- {
- properties[nameof(InitialMSBuildServerState)] = InitialMSBuildServerState;
- }
-
- if (ProjectPath != null)
- {
- properties[nameof(ProjectPath)] = ProjectPath;
- }
-
- if (ServerFallbackReason != null)
- {
- properties[nameof(ServerFallbackReason)] = ServerFallbackReason;
- }
-
- if (BuildSuccess.HasValue)
- {
- properties[nameof(BuildSuccess)] = BuildSuccess.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- if (BuildTarget != null)
- {
- properties[nameof(BuildTarget)] = BuildTarget;
- }
-
- if (BuildEngineVersion != null)
- {
- properties[nameof(BuildEngineVersion)] = BuildEngineVersion.ToString();
- }
+ AddIfNotNull(BuildEngineHost);
+ AddIfNotNull(BuildSuccess);
+ AddIfNotNull(BuildTarget);
+ AddIfNotNull(BuildEngineVersion);
+ AddIfNotNull(BuildCheckEnabled);
+ AddIfNotNull(MultiThreadedModeEnabled);
+ AddIfNotNull(SACEnabled);
+ AddIfNotNull(IsStandaloneExecution);
- if (BuildCheckEnabled != null)
- {
- properties[nameof(BuildCheckEnabled)] = BuildCheckEnabled.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- if (MultiThreadedModeEnabled != null)
- {
- properties[nameof(MultiThreadedModeEnabled)] = MultiThreadedModeEnabled.Value.ToString(CultureInfo.InvariantCulture);
- }
+ return telemetryItems;
- if (SACEnabled != null)
+ void AddIfNotNull(object? value, [CallerArgumentExpression(nameof(value))] string key = "")
{
- properties[nameof(SACEnabled)] = SACEnabled.Value.ToString(CultureInfo.InvariantCulture);
+ if (value != null)
+ {
+ telemetryItems.Add(key, value);
+ }
}
-
- return properties;
}
- ///
- /// Create a list of properties sent to VS telemetry with the information whether they should be hashed.
- ///
- ///
- public IList GetActivityProperties()
+ public override IDictionary GetProperties()
{
- List telemetryItems = new(8);
+ var properties = new Dictionary();
+ AddIfNotNull(BuildEngineDisplayVersion);
+ AddIfNotNull(BuildEngineFrameworkName);
+ AddIfNotNull(BuildEngineHost);
+ AddIfNotNull(InitialMSBuildServerState);
+ AddIfNotNull(ProjectPath);
+ AddIfNotNull(ServerFallbackReason);
+ AddIfNotNull(BuildTarget);
+ AddIfNotNull(BuildEngineVersion?.ToString(), nameof(BuildEngineVersion));
+ AddIfNotNull(BuildSuccess?.ToString(), nameof(BuildSuccess));
+ AddIfNotNull(BuildCheckEnabled?.ToString(), nameof(BuildCheckEnabled));
+ AddIfNotNull(MultiThreadedModeEnabled?.ToString(), nameof(MultiThreadedModeEnabled));
+ AddIfNotNull(SACEnabled?.ToString(), nameof(SACEnabled));
+ AddIfNotNull(IsStandaloneExecution?.ToString(), nameof(IsStandaloneExecution));
+
+ // Calculate durations
if (StartAt.HasValue && FinishedAt.HasValue)
{
- telemetryItems.Add(new TelemetryItem(TelemetryConstants.BuildDurationPropertyName, (FinishedAt.Value - StartAt.Value).TotalMilliseconds, false));
+ properties[TelemetryConstants.BuildDurationPropertyName] =
+ (FinishedAt.Value - StartAt.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
}
if (InnerStartAt.HasValue && FinishedAt.HasValue)
{
- telemetryItems.Add(new TelemetryItem(TelemetryConstants.InnerBuildDurationPropertyName, (FinishedAt.Value - InnerStartAt.Value).TotalMilliseconds, false));
- }
-
- if (BuildEngineHost != null)
- {
- telemetryItems.Add(new TelemetryItem(nameof(BuildEngineHost), BuildEngineHost, false));
- }
-
- if (BuildSuccess.HasValue)
- {
- telemetryItems.Add(new TelemetryItem(nameof(BuildSuccess), BuildSuccess, false));
- }
-
- if (BuildTarget != null)
- {
- telemetryItems.Add(new TelemetryItem(nameof(BuildTarget), BuildTarget, true));
- }
-
- if (BuildEngineVersion != null)
- {
- telemetryItems.Add(new TelemetryItem(nameof(BuildEngineVersion), BuildEngineVersion.ToString(), false));
+ properties[TelemetryConstants.InnerBuildDurationPropertyName] =
+ (FinishedAt.Value - InnerStartAt.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
}
- if (BuildCheckEnabled != null)
- {
- telemetryItems.Add(new TelemetryItem(nameof(BuildCheckEnabled), BuildCheckEnabled, false));
- }
-
- if (MultiThreadedModeEnabled != null)
- {
- telemetryItems.Add(new TelemetryItem(nameof(MultiThreadedModeEnabled), MultiThreadedModeEnabled, false));
- }
+ return properties;
- if (SACEnabled != null)
+ void AddIfNotNull(string? value, [CallerArgumentExpression(nameof(value))] string key = "")
{
- telemetryItems.Add(new TelemetryItem(nameof(SACEnabled), SACEnabled, false));
+ if (value != null)
+ {
+ properties[key] = value;
+ }
}
-
- return telemetryItems;
}
}
}
diff --git a/src/Framework/Telemetry/DiagnosticActivity.cs b/src/Framework/Telemetry/DiagnosticActivity.cs
new file mode 100644
index 00000000000..3bb2ed30f8e
--- /dev/null
+++ b/src/Framework/Telemetry/DiagnosticActivity.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if !NETFRAMEWORK
+
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Microsoft.Build.Framework.Telemetry
+{
+ ///
+ /// Wraps a and implements .
+ ///
+ internal class DiagnosticActivity : IActivity
+ {
+ private readonly Activity _activity;
+ private bool _disposed;
+
+ public DiagnosticActivity(Activity activity)
+ {
+ _activity = activity;
+ }
+
+ public IActivity? SetTags(IActivityTelemetryDataHolder? dataHolder)
+ {
+ Dictionary? tags = dataHolder?.GetActivityProperties();
+ if (tags != null)
+ {
+ foreach (KeyValuePair tag in tags)
+ {
+ SetTag(tag.Key, tag.Value);
+ }
+ }
+
+ return this;
+ }
+
+ public IActivity? SetTag(string key, object? value)
+ {
+ if (value != null)
+ {
+ _activity.SetTag($"{TelemetryConstants.PropertyPrefix}{key}", value);
+ }
+
+ return this;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _activity.Dispose();
+
+ _disposed = true;
+ }
+ }
+}
+
+#endif
diff --git a/src/Framework/Telemetry/IActivity.cs b/src/Framework/Telemetry/IActivity.cs
new file mode 100644
index 00000000000..6118e50f7e8
--- /dev/null
+++ b/src/Framework/Telemetry/IActivity.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Build.Framework.Telemetry
+{
+ ///
+ /// Represents an activity for telemetry tracking.
+ ///
+ internal interface IActivity : IDisposable
+ {
+ ///
+ /// Sets a tag on the activity.
+ ///
+ /// Telemetry data holder.
+ /// The activity instance for method chaining.
+ IActivity? SetTags(IActivityTelemetryDataHolder? dataHolder);
+
+ ///
+ /// Sets a tag on the activity.
+ ///
+ /// The tag key.
+ /// The tag value.
+ /// The activity instance for method chaining.
+ IActivity? SetTag(string key, object? value);
+ }
+}
diff --git a/src/Framework/Telemetry/IActivityTelemetryDataHolder.cs b/src/Framework/Telemetry/IActivityTelemetryDataHolder.cs
index 9eeb0a7509f..e660f191695 100644
--- a/src/Framework/Telemetry/IActivityTelemetryDataHolder.cs
+++ b/src/Framework/Telemetry/IActivityTelemetryDataHolder.cs
@@ -2,14 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
-using System.Diagnostics;
namespace Microsoft.Build.Framework.Telemetry;
///
-/// Interface for classes that hold telemetry data that should be added as tags to an .
+/// Interface for classes that hold telemetry data that should be added as tags to an .
///
internal interface IActivityTelemetryDataHolder
{
- IList GetActivityProperties();
+ Dictionary GetActivityProperties();
}
diff --git a/src/Framework/Telemetry/IWorkerNodeTelemetryData.cs b/src/Framework/Telemetry/IWorkerNodeTelemetryData.cs
index a0303e4a4e2..b4ca028d57d 100644
--- a/src/Framework/Telemetry/IWorkerNodeTelemetryData.cs
+++ b/src/Framework/Telemetry/IWorkerNodeTelemetryData.cs
@@ -8,5 +8,6 @@ namespace Microsoft.Build.Framework.Telemetry;
internal interface IWorkerNodeTelemetryData
{
Dictionary TasksExecutionData { get; }
+
Dictionary TargetsExecutionData { get; }
}
diff --git a/src/Framework/Telemetry/MSBuildActivitySource.cs b/src/Framework/Telemetry/MSBuildActivitySource.cs
index 7d73f87062f..891e85c781f 100644
--- a/src/Framework/Telemetry/MSBuildActivitySource.cs
+++ b/src/Framework/Telemetry/MSBuildActivitySource.cs
@@ -1,36 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#if NETFRAMEWORK
+using Microsoft.VisualStudio.Telemetry;
+#else
using System.Diagnostics;
+#endif
namespace Microsoft.Build.Framework.Telemetry
{
///
- /// Wrapper class for ActivitySource with a method that wraps Activity name with VS OTel prefix.
+ /// Wrapper class for ActivitySource with a method that wraps Activity name with MSBuild prefix.
+ /// On .NET Framework, activities are also forwarded to VS Telemetry.
///
internal class MSBuildActivitySource
{
+#if NETFRAMEWORK
+ private readonly TelemetrySession? _telemetrySession;
+
+ public MSBuildActivitySource(TelemetrySession? telemetrySession)
+ {
+ _telemetrySession = telemetrySession;
+ }
+#else
private readonly ActivitySource _source;
- private readonly double _sampleRate;
- public MSBuildActivitySource(string name, double sampleRate)
+ public MSBuildActivitySource(string name)
{
_source = new ActivitySource(name);
- _sampleRate = sampleRate;
}
+#endif
+
///
- /// Prefixes activity with VS OpenTelemetry.
+ /// Starts a new activity with the appropriate telemetry prefix.
///
/// Name of the telemetry event without prefix.
- ///
- public Activity? StartActivity(string name)
+ /// An wrapping the underlying Activity, or null if not sampled.
+ public IActivity? StartActivity(string name)
{
- var activity = Activity.Current?.HasRemoteParent == true
- ? _source.StartActivity($"{TelemetryConstants.EventPrefix}{name}", ActivityKind.Internal, parentId: Activity.Current.ParentId)
- : _source.StartActivity($"{TelemetryConstants.EventPrefix}{name}");
- activity?.WithTag(new("SampleRate", _sampleRate, false));
+ string eventName = $"{TelemetryConstants.EventPrefix}{name}";
+
+#if NETFRAMEWORK
+ TelemetryScope? operation = _telemetrySession?.StartOperation(eventName);
+ return operation != null ? new VsTelemetryActivity(operation) : null;
+#else
+ Activity? activity = Activity.Current?.HasRemoteParent == true
+ ? _source.StartActivity(eventName, ActivityKind.Internal, parentId: Activity.Current.ParentId)
+ : _source.StartActivity(eventName);
+
+ if (activity == null)
+ {
+ return null;
+ }
+
+ activity.SetTag("SampleRate", TelemetryConstants.DefaultSampleRate);
- return activity;
+ return new DiagnosticActivity(activity);
+#endif
}
}
}
diff --git a/src/Framework/Telemetry/OpenTelemetryManager.cs b/src/Framework/Telemetry/OpenTelemetryManager.cs
deleted file mode 100644
index 7ee7813bddf..00000000000
--- a/src/Framework/Telemetry/OpenTelemetryManager.cs
+++ /dev/null
@@ -1,282 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-#if NETFRAMEWORK
-using Microsoft.VisualStudio.OpenTelemetry.ClientExtensions;
-using Microsoft.VisualStudio.OpenTelemetry.ClientExtensions.Exporters;
-using Microsoft.VisualStudio.OpenTelemetry.Collector.Interfaces;
-using Microsoft.VisualStudio.OpenTelemetry.Collector.Settings;
-using OpenTelemetry;
-using OpenTelemetry.Trace;
-#endif
-using System;
-using System.Runtime.CompilerServices;
-using System.Threading;
-
-namespace Microsoft.Build.Framework.Telemetry
-{
-
- ///
- /// Singleton class for configuring and managing the telemetry infrastructure with System.Diagnostics.Activity,
- /// OpenTelemetry SDK, and VS OpenTelemetry Collector.
- ///
- internal class OpenTelemetryManager
- {
- // Lazy provides thread-safe lazy initialization.
- private static readonly Lazy s_instance =
- new Lazy(() => new OpenTelemetryManager(), LazyThreadSafetyMode.ExecutionAndPublication);
-
- ///
- /// Globally accessible instance of .
- ///
- public static OpenTelemetryManager Instance => s_instance.Value;
-
- private TelemetryState _telemetryState = TelemetryState.Uninitialized;
- private readonly LockType _initializeLock = new LockType();
- private double _sampleRate = TelemetryConstants.DefaultSampleRate;
-
-#if NETFRAMEWORK
- private TracerProvider? _tracerProvider;
- private IOpenTelemetryCollector? _collector;
-#endif
-
- public string? LoadFailureExceptionMessage { get; set; }
-
- ///
- /// Optional activity source for MSBuild or other telemetry usage.
- ///
- public MSBuildActivitySource? DefaultActivitySource { get; private set; }
-
- private OpenTelemetryManager()
- {
- }
-
- ///
- /// Initializes the telemetry infrastructure. Multiple invocations are no-op, thread-safe.
- ///
- /// Differentiates between executing as MSBuild.exe or from VS/API.
- public void Initialize(bool isStandalone)
- {
- // for lock free early exit
- if (_telemetryState != TelemetryState.Uninitialized)
- {
- return;
- }
-
- lock (_initializeLock)
- {
- // for correctness
- if (_telemetryState != TelemetryState.Uninitialized)
- {
- return;
- }
-
- if (IsOptOut())
- {
- _telemetryState = TelemetryState.OptOut;
- return;
- }
-
- // TODO: temporary until we have green light to enable telemetry perf-wise
- if (!IsOptIn())
- {
- _telemetryState = TelemetryState.Unsampled;
- return;
- }
-
- if (!IsSampled())
- {
- _telemetryState = TelemetryState.Unsampled;
- return;
- }
-
- InitializeActivitySources();
- }
-#if NETFRAMEWORK
- try
- {
- InitializeTracerProvider();
-
- // TODO: Enable commented logic when Collector is present in VS
- // if (isStandalone)
- InitializeCollector();
-
- // }
- }
- catch (Exception ex) when (ex is System.IO.FileNotFoundException or System.IO.FileLoadException)
- {
- // catch exceptions from loading the OTel SDK or Collector to maintain usability of Microsoft.Build.Framework package in our and downstream tests in VS.
- _telemetryState = TelemetryState.Unsampled;
- LoadFailureExceptionMessage = ex.ToString();
- return;
- }
-#endif
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)] // avoid assembly loads
- private void InitializeActivitySources()
- {
- _telemetryState = TelemetryState.TracerInitialized;
- DefaultActivitySource = new MSBuildActivitySource(TelemetryConstants.DefaultActivitySourceNamespace, _sampleRate);
- }
-
-#if NETFRAMEWORK
- ///
- /// Initializes the OpenTelemetry SDK TracerProvider with VS default exporter settings.
- ///
- [MethodImpl(MethodImplOptions.NoInlining)] // avoid assembly loads
- private void InitializeTracerProvider()
- {
- var exporterSettings = OpenTelemetryExporterSettingsBuilder
- .CreateVSDefault(TelemetryConstants.VSMajorVersion)
- .Build();
-
- TracerProviderBuilder tracerProviderBuilder = Sdk
- .CreateTracerProviderBuilder()
- // this adds listeners to ActivitySources with the prefix "Microsoft.VisualStudio.OpenTelemetry."
- .AddVisualStudioDefaultTraceExporter(exporterSettings);
-
- _tracerProvider = tracerProviderBuilder.Build();
- _telemetryState = TelemetryState.ExporterInitialized;
- }
-
- ///
- /// Initializes the VS OpenTelemetry Collector with VS default settings.
- ///
- [MethodImpl(MethodImplOptions.NoInlining)] // avoid assembly loads
- private void InitializeCollector()
- {
- IOpenTelemetryCollectorSettings collectorSettings = OpenTelemetryCollectorSettingsBuilder
- .CreateVSDefault(TelemetryConstants.VSMajorVersion)
- .Build();
-
- _collector = OpenTelemetryCollectorProvider.CreateCollector(collectorSettings);
- _collector.StartAsync().GetAwaiter().GetResult();
-
- _telemetryState = TelemetryState.CollectorInitialized;
- }
-#endif
- [MethodImpl(MethodImplOptions.NoInlining)] // avoid assembly loads
- private void ForceFlushInner()
- {
-#if NETFRAMEWORK
- _tracerProvider?.ForceFlush();
-#endif
- }
-
- ///
- /// Flush the telemetry in TracerProvider/Exporter.
- ///
- public void ForceFlush()
- {
- if (ShouldBeCleanedUp())
- {
- ForceFlushInner();
- }
- }
-
- // to avoid assembly loading OpenTelemetry in tests
- [MethodImpl(MethodImplOptions.NoInlining)] // avoid assembly loads
- private void ShutdownInner()
- {
-#if NETFRAMEWORK
- _tracerProvider?.Shutdown();
- // Dispose stops the collector, with a default drain timeout of 10s
- _collector?.Dispose();
-#endif
- }
-
- ///
- /// Shuts down the telemetry infrastructure.
- ///
- public void Shutdown()
- {
- lock (_initializeLock)
- {
- if (ShouldBeCleanedUp())
- {
- ShutdownInner();
- }
-
- _telemetryState = TelemetryState.Disposed;
- }
- }
-
- ///
- /// Determines if the user has explicitly opted out of telemetry.
- ///
- private bool IsOptOut() => Traits.Instance.FrameworkTelemetryOptOut || Traits.Instance.SdkTelemetryOptOut || !ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_14);
-
- ///
- /// TODO: Temporary until perf of loading OTel is agreed to in VS.
- ///
- private bool IsOptIn() => !IsOptOut() && (Traits.Instance.TelemetryOptIn || Traits.Instance.TelemetrySampleRateOverride.HasValue);
-
- ///
- /// Determines if telemetry should be initialized based on sampling and environment variable overrides.
- ///
- private bool IsSampled()
- {
- double? overrideRate = Traits.Instance.TelemetrySampleRateOverride;
- if (overrideRate.HasValue)
- {
- _sampleRate = overrideRate.Value;
- }
- else
- {
-#if !NETFRAMEWORK
- // In core, OTel infrastructure is not initialized by default.
- return false;
-#endif
- }
-
- // Simple random sampling, this method is called once, no need to save the Random instance.
- Random random = new();
- return random.NextDouble() < _sampleRate;
- }
-
- private bool ShouldBeCleanedUp() => _telemetryState == TelemetryState.CollectorInitialized || _telemetryState == TelemetryState.ExporterInitialized;
-
- internal bool IsActive() => _telemetryState == TelemetryState.TracerInitialized || _telemetryState == TelemetryState.CollectorInitialized || _telemetryState == TelemetryState.ExporterInitialized;
-
- ///
- /// State of the telemetry infrastructure.
- ///
- internal enum TelemetryState
- {
- ///
- /// Initial state.
- ///
- Uninitialized,
-
- ///
- /// Opt out of telemetry.
- ///
- OptOut,
-
- ///
- /// Run not sampled for telemetry.
- ///
- Unsampled,
-
- ///
- /// For core hook, ActivitySource is created.
- ///
- TracerInitialized,
-
- ///
- /// For VS scenario with a collector. ActivitySource, OTel TracerProvider are created.
- ///
- ExporterInitialized,
-
- ///
- /// For standalone, ActivitySource, OTel TracerProvider, VS OpenTelemetry Collector are created.
- ///
- CollectorInitialized,
-
- ///
- /// End state.
- ///
- Disposed
- }
- }
-}
diff --git a/src/Framework/Telemetry/TelemetryConstants.cs b/src/Framework/Telemetry/TelemetryConstants.cs
index dc51085f60c..94e194b9e48 100644
--- a/src/Framework/Telemetry/TelemetryConstants.cs
+++ b/src/Framework/Telemetry/TelemetryConstants.cs
@@ -3,20 +3,10 @@
namespace Microsoft.Build.Framework.Telemetry;
///
-/// Constants for VS OpenTelemetry for basic configuration and appropriate naming for VS exporting/collection.
+/// Constants for VS Telemetry for basic configuration and appropriate naming for VS exporting/collection.
///
internal static class TelemetryConstants
{
- ///
- /// "Microsoft.VisualStudio.OpenTelemetry.*" namespace is required by VS exporting/collection.
- ///
- public const string ActivitySourceNamespacePrefix = "Microsoft.VisualStudio.OpenTelemetry.MSBuild.";
-
- ///
- /// Namespace of the default ActivitySource handling e.g. End of build telemetry.
- ///
- public const string DefaultActivitySourceNamespace = $"{ActivitySourceNamespacePrefix}Default";
-
///
/// Prefix required by VS exporting/collection.
///
@@ -28,9 +18,14 @@ internal static class TelemetryConstants
public const string PropertyPrefix = "VS.MSBuild.";
///
- /// For VS OpenTelemetry Collector to apply the correct privacy policy.
+ /// "Microsoft.Build.Telemetry.*" namespace is required by VS exporting/collection.
///
- public const string VSMajorVersion = "18.0";
+ public const string ActivitySourceNamespacePrefix = "Microsoft.Build.Telemetry";
+
+ ///
+ /// Namespace of the default ActivitySource handling e.g. End of build telemetry.
+ ///
+ public const string DefaultActivitySourceNamespace = $"{ActivitySourceNamespacePrefix}Default";
///
/// Sample rate for the default namespace.
@@ -47,13 +42,9 @@ internal static class TelemetryConstants
/// Name of the property for inner build duration.
///
public const string InnerBuildDurationPropertyName = "InnerBuildDurationInMilliseconds";
-}
-internal static class NodeTelemetryTags
-{
- // These properties can't use nameof since they're not tied to a specific class property
- public const string Tasks = "Tasks";
- public const string Targets = "Targets";
- public const string TargetsSummary = "TargetsSummary";
- public const string TasksSummary = "TasksSummary";
+ ///
+ /// Name of the property for build activity.
+ ///
+ public const string Build = "Build";
}
diff --git a/src/Framework/Telemetry/TelemetryDataUtils.cs b/src/Framework/Telemetry/TelemetryDataUtils.cs
new file mode 100644
index 00000000000..b7202bd897b
--- /dev/null
+++ b/src/Framework/Telemetry/TelemetryDataUtils.cs
@@ -0,0 +1,313 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+using static Microsoft.Build.Framework.Telemetry.BuildInsights;
+
+namespace Microsoft.Build.Framework.Telemetry
+{
+ internal static class TelemetryDataUtils
+ {
+ ///
+ /// Transforms collected telemetry data to format recognized by the telemetry infrastructure.
+ ///
+ /// Data about tasks and target forwarded from nodes.
+ /// Controls whether Task details should attached to the telemetry.
+ /// Controls whether Target details should be attached to the telemetry.
+ /// Node Telemetry data wrapped in a list of properties that can be attached as tags to a .
+ public static IActivityTelemetryDataHolder? AsActivityDataHolder(this IWorkerNodeTelemetryData? telemetryData, bool includeTasksDetails, bool includeTargetDetails)
+ {
+ if (telemetryData == null)
+ {
+ return null;
+ }
+
+ var targetsSummary = new TargetsSummaryConverter();
+ targetsSummary.Process(telemetryData.TargetsExecutionData);
+
+ var tasksSummary = new TasksSummaryConverter();
+ tasksSummary.Process(telemetryData.TasksExecutionData);
+
+ var buildInsights = new BuildInsights(
+ includeTasksDetails ? GetTasksDetails(telemetryData.TasksExecutionData) : [],
+ includeTargetDetails ? GetTargetsDetails(telemetryData.TargetsExecutionData) : [],
+ GetTargetsSummary(targetsSummary),
+ GetTasksSummary(tasksSummary));
+
+ return new NodeTelemetry(buildInsights);
+ }
+
+ ///
+ /// Converts targets details to a list of custom objects for telemetry.
+ ///
+ private static List GetTargetsDetails(Dictionary targetsDetails)
+ {
+ var result = new List();
+
+ foreach (KeyValuePair valuePair in targetsDetails)
+ {
+ string targetName = ShouldHashKey(valuePair.Key) ? GetHashed(valuePair.Key.Name) : valuePair.Key.Name;
+
+ result.Add(new TargetDetailInfo(
+ targetName,
+ valuePair.Value,
+ valuePair.Key.IsCustom,
+ valuePair.Key.IsNuget,
+ valuePair.Key.IsMetaProj));
+ }
+
+ return result;
+
+ static bool ShouldHashKey(TaskOrTargetTelemetryKey key) => key.IsCustom || key.IsMetaProj;
+ }
+
+ internal record TargetDetailInfo(string Name, bool WasExecuted, bool IsCustom, bool IsNuget, bool IsMetaProj);
+
+ ///
+ /// Converts tasks details to a list of custom objects for telemetry.
+ ///
+ private static List GetTasksDetails(
+ Dictionary tasksDetails)
+ {
+ var result = new List();
+
+ foreach (KeyValuePair valuePair in tasksDetails)
+ {
+ string taskName = valuePair.Key.IsCustom ? GetHashed(valuePair.Key.Name) : valuePair.Key.Name;
+
+ result.Add(new TaskDetailInfo(
+ taskName,
+ valuePair.Value.CumulativeExecutionTime.TotalMilliseconds,
+ valuePair.Value.ExecutionsCount,
+ valuePair.Value.TotalMemoryBytes,
+ valuePair.Key.IsCustom,
+ valuePair.Key.IsNuget));
+ }
+
+ return result;
+ }
+
+ ///
+ /// Depending on the platform, hash the value using an available mechanism.
+ ///
+ internal static string GetHashed(object value) => Sha256Hasher.Hash(value?.ToString() ?? "");
+
+ // https://github.com/dotnet/sdk/blob/8bd19a2390a6bba4aa80d1ac3b6c5385527cc311/src/Cli/Microsoft.DotNet.Cli.Utils/Sha256Hasher.cs + workaround for netstandard2.0
+ private static class Sha256Hasher
+ {
+ ///
+ /// The hashed mac address needs to be the same hashed value as produced by the other distinct sources given the same input. (e.g. VsCode)
+ ///
+ public static string Hash(string text)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(text);
+#if NET
+ byte[] hash = SHA256.HashData(bytes);
+#if NET9_0_OR_GREATER
+ return System.Convert.ToHexStringLower(hash);
+#else
+ return Convert.ToHexString(hash).ToLowerInvariant();
+#endif
+
+#else
+ // Create the SHA256 object and compute the hash
+ using (var sha256 = SHA256.Create())
+ {
+ byte[] hash = sha256.ComputeHash(bytes);
+
+ // Convert the hash bytes to a lowercase hex string (manual loop approach)
+ var sb = new StringBuilder(hash.Length * 2);
+ foreach (byte b in hash)
+ {
+ sb.AppendFormat("{0:x2}", b);
+ }
+
+ return sb.ToString();
+ }
+#endif
+ }
+ }
+
+ internal record TaskDetailInfo(string Name, double TotalMilliseconds, int ExecutionsCount, long TotalMemoryBytes, bool IsCustom, bool IsNuget);
+
+ ///
+ /// Converts targets summary to a custom object for telemetry.
+ ///
+ private static TargetsSummaryInfo GetTargetsSummary(TargetsSummaryConverter summary)
+ {
+ return new TargetsSummaryInfo(
+ CreateTargetStats(summary.LoadedBuiltinTargetInfo, summary.LoadedCustomTargetInfo),
+ CreateTargetStats(summary.ExecutedBuiltinTargetInfo, summary.ExecutedCustomTargetInfo));
+
+ static TargetStatsInfo CreateTargetStats(
+ TargetsSummaryConverter.TargetInfo builtinInfo,
+ TargetsSummaryConverter.TargetInfo customInfo)
+ {
+ var microsoft = builtinInfo.Total > 0
+ ? new TargetCategoryInfo(builtinInfo.Total, builtinInfo.FromNuget, builtinInfo.FromMetaproj)
+ : null;
+
+ var custom = customInfo.Total > 0
+ ? new TargetCategoryInfo(customInfo.Total, customInfo.FromNuget, customInfo.FromMetaproj)
+ : null;
+
+ return new TargetStatsInfo(builtinInfo.Total + customInfo.Total, microsoft, custom);
+ }
+ }
+
+ internal record TargetsSummaryInfo(TargetStatsInfo Loaded, TargetStatsInfo Executed);
+
+ internal record TargetStatsInfo(int Total, TargetCategoryInfo? Microsoft, TargetCategoryInfo? Custom);
+
+ internal record TargetCategoryInfo(int Total, int FromNuget, int FromMetaproj);
+
+ ///
+ /// Converts tasks summary to a custom object for telemetry.
+ ///
+ private static TasksSummaryInfo GetTasksSummary(TasksSummaryConverter summary)
+ {
+ var microsoft = CreateTaskStats(summary.BuiltinTasksInfo.Total, summary.BuiltinTasksInfo.FromNuget);
+ var custom = CreateTaskStats(summary.CustomTasksInfo.Total, summary.CustomTasksInfo.FromNuget);
+
+ return new TasksSummaryInfo(microsoft, custom);
+
+ static TaskCategoryStats? CreateTaskStats(TaskExecutionStats total, TaskExecutionStats fromNuget)
+ {
+ var totalStats = total.ExecutionsCount > 0
+ ? new TaskStatsInfo(
+ total.ExecutionsCount,
+ total.CumulativeExecutionTime.TotalMilliseconds,
+ total.TotalMemoryBytes)
+ : null;
+
+ var nugetStats = fromNuget.ExecutionsCount > 0
+ ? new TaskStatsInfo(
+ fromNuget.ExecutionsCount,
+ fromNuget.CumulativeExecutionTime.TotalMilliseconds,
+ fromNuget.TotalMemoryBytes)
+ : null;
+
+ return (totalStats != null || nugetStats != null)
+ ? new TaskCategoryStats(totalStats, nugetStats)
+ : null;
+ }
+ }
+
+ private class TargetsSummaryConverter
+ {
+ internal TargetInfo LoadedBuiltinTargetInfo { get; } = new();
+
+ internal TargetInfo LoadedCustomTargetInfo { get; } = new();
+
+ internal TargetInfo ExecutedBuiltinTargetInfo { get; } = new();
+
+ internal TargetInfo ExecutedCustomTargetInfo { get; } = new();
+
+ ///
+ /// Processes target execution data to compile summary statistics for both built-in and custom targets.
+ ///
+ public void Process(Dictionary targetsExecutionData)
+ {
+ foreach (var kv in targetsExecutionData)
+ {
+ GetTargetInfo(kv.Key, isExecuted: false).Increment(kv.Key);
+
+ // Update executed targets statistics (only if executed)
+ if (kv.Value)
+ {
+ GetTargetInfo(kv.Key, isExecuted: true).Increment(kv.Key);
+ }
+ }
+ }
+
+ private TargetInfo GetTargetInfo(TaskOrTargetTelemetryKey key, bool isExecuted) =>
+ (key.IsCustom, isExecuted) switch
+ {
+ (true, true) => ExecutedCustomTargetInfo,
+ (true, false) => LoadedCustomTargetInfo,
+ (false, true) => ExecutedBuiltinTargetInfo,
+ (false, false) => LoadedBuiltinTargetInfo,
+ };
+
+ internal class TargetInfo
+ {
+ public int Total { get; private set; }
+
+ public int FromNuget { get; private set; }
+
+ public int FromMetaproj { get; private set; }
+
+ internal void Increment(TaskOrTargetTelemetryKey key)
+ {
+ Total++;
+ if (key.IsNuget)
+ {
+ FromNuget++;
+ }
+
+ if (key.IsMetaProj)
+ {
+ FromMetaproj++;
+ }
+ }
+ }
+ }
+
+ private class TasksSummaryConverter
+ {
+ internal TasksInfo BuiltinTasksInfo { get; } = new();
+
+ internal TasksInfo CustomTasksInfo { get; } = new();
+
+ ///
+ /// Processes task execution data to compile summary statistics for both built-in and custom tasks.
+ ///
+ public void Process(Dictionary tasksExecutionData)
+ {
+ foreach (KeyValuePair kv in tasksExecutionData)
+ {
+ var taskInfo = kv.Key.IsCustom ? CustomTasksInfo : BuiltinTasksInfo;
+ taskInfo.Total.Accumulate(kv.Value);
+
+ if (kv.Key.IsNuget)
+ {
+ taskInfo.FromNuget.Accumulate(kv.Value);
+ }
+ }
+ }
+
+ internal class TasksInfo
+ {
+ public TaskExecutionStats Total { get; } = TaskExecutionStats.CreateEmpty();
+
+ public TaskExecutionStats FromNuget { get; } = TaskExecutionStats.CreateEmpty();
+ }
+ }
+
+ private sealed class NodeTelemetry(BuildInsights insights) : IActivityTelemetryDataHolder
+ {
+ Dictionary IActivityTelemetryDataHolder.GetActivityProperties()
+ {
+ Dictionary properties = new()
+ {
+ [nameof(BuildInsights.TargetsSummary)] = insights.TargetsSummary,
+ [nameof(BuildInsights.TasksSummary)] = insights.TasksSummary,
+ };
+
+ if (insights.Targets.Count > 0)
+ {
+ properties[nameof(BuildInsights.Targets)] = insights.Targets;
+ }
+
+ if (insights.Tasks.Count > 0)
+ {
+ properties[nameof(BuildInsights.Tasks)] = insights.Tasks;
+ }
+
+ return properties;
+ }
+ }
+ }
+}
diff --git a/src/Framework/Telemetry/TelemetryItem.cs b/src/Framework/Telemetry/TelemetryItem.cs
deleted file mode 100644
index f037d7ddbea..00000000000
--- a/src/Framework/Telemetry/TelemetryItem.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace Microsoft.Build.Framework.Telemetry;
-
-internal record TelemetryItem(string Name, object Value, bool NeedsHashing);
diff --git a/src/Framework/Telemetry/TelemetryManager.cs b/src/Framework/Telemetry/TelemetryManager.cs
new file mode 100644
index 00000000000..4b55507436b
--- /dev/null
+++ b/src/Framework/Telemetry/TelemetryManager.cs
@@ -0,0 +1,195 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NETFRAMEWORK
+using Microsoft.VisualStudio.Telemetry;
+#endif
+
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.Build.Framework.Telemetry
+{
+ ///
+ /// Manages telemetry collection and reporting for MSBuild.
+ /// This class provides a centralized way to initialize, configure, and manage telemetry sessions.
+ ///
+ ///
+ /// The TelemetryManager is a singleton that handles both standalone and integrated telemetry scenarios.
+ /// On .NET Framework, it integrates with Visual Studio telemetry services.
+ /// On .NET Core it provides a lightweight telemetry implementation through exposing an activity source.
+ ///
+ internal class TelemetryManager
+ {
+ ///
+ /// Lock object for thread-safe initialization and disposal.
+ ///
+ private static readonly LockType s_lock = new();
+
+ private static bool s_initialized;
+ private static bool s_disposed;
+
+ private TelemetryManager()
+ {
+ }
+
+ ///
+ /// Optional activity source for MSBuild or other telemetry usage.
+ ///
+ public MSBuildActivitySource? DefaultActivitySource { get; private set; }
+
+ public static TelemetryManager Instance { get; } = new TelemetryManager();
+
+ ///
+ /// Initializes the telemetry manager with the specified configuration.
+ ///
+ ///
+ /// Indicates whether MSBuild is running in standalone mode (e.g., MSBuild.exe directly invoked)
+ /// versus integrated mode (e.g., running within Visual Studio or dotnet CLI).
+ /// When true, creates and manages its own telemetry session on .NET Framework.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void Initialize(bool isStandalone)
+ {
+ lock (s_lock)
+ {
+ if (s_initialized)
+ {
+ return;
+ }
+
+ s_initialized = true;
+
+ if (IsOptOut())
+ {
+ return;
+ }
+
+ TryInitializeTelemetry(isStandalone);
+ }
+ }
+
+ ///
+ /// Resets the TelemetryManager state for TESTING purposes.
+ ///
+ internal static void ResetForTest()
+ {
+ lock (s_lock)
+ {
+ s_initialized = false;
+ s_disposed = false;
+ Instance.DefaultActivitySource = null;
+ }
+ }
+
+ ///
+ /// Initializes MSBuild telemetry.
+ /// This method is deliberately not inlined to ensure
+ /// the Telemetry related assemblies are only loaded when this method is called,
+ /// allowing the calling code to catch assembly loading exceptions.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void TryInitializeTelemetry(bool isStandalone)
+ {
+ try
+ {
+#if NETFRAMEWORK
+ DefaultActivitySource = VsTelemetryInitializer.Initialize(isStandalone);
+#else
+ DefaultActivitySource = new MSBuildActivitySource(TelemetryConstants.DefaultActivitySourceNamespace);
+#endif
+ }
+ catch (Exception ex) when (ex is FileNotFoundException or FileLoadException or TypeLoadException)
+ {
+ // Microsoft.VisualStudio.Telemetry or System.Diagnostics.DiagnosticSource might not be available outside of VS or dotnet.
+ // This is expected in standalone application scenarios (when MSBuild.exe is invoked directly).
+ DefaultActivitySource = null;
+ }
+ }
+
+ public void Dispose()
+ {
+ lock (s_lock)
+ {
+ if (s_disposed)
+ {
+ return;
+ }
+
+#if NETFRAMEWORK
+ try
+ {
+ DisposeVsTelemetry();
+ }
+ catch (Exception ex) when (
+ ex is FileNotFoundException or
+ FileLoadException or
+ TypeLoadException)
+ {
+ // Assembly was never loaded, nothing to dispose.
+ }
+#endif
+ s_disposed = true;
+ }
+ }
+
+ ///
+ /// Determines if the user has explicitly opted out of telemetry.
+ ///
+ internal static bool IsOptOut() =>
+#if NETFRAMEWORK
+ Traits.Instance.FrameworkTelemetryOptOut;
+#else
+ Traits.Instance.SdkTelemetryOptOut;
+#endif
+
+#if NETFRAMEWORK
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void DisposeVsTelemetry() => VsTelemetryInitializer.Dispose();
+#endif
+ }
+
+#if NETFRAMEWORK
+ internal static class VsTelemetryInitializer
+ {
+ // Telemetry API key for Visual Studio telemetry service.
+ private const string CollectorApiKey = "f3e86b4023cc43f0be495508d51f588a-f70d0e59-0fb0-4473-9f19-b4024cc340be-7296";
+
+ // Store as object to avoid type reference at class load time
+ private static object? s_telemetrySession;
+ private static bool s_ownsSession = false;
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static MSBuildActivitySource Initialize(bool isStandalone)
+ {
+ TelemetrySession session;
+ if (isStandalone)
+ {
+ session = TelemetryService.CreateAndGetDefaultSession(CollectorApiKey);
+ TelemetryService.DefaultSession.UseVsIsOptedIn();
+ TelemetryService.DefaultSession.Start();
+ s_ownsSession = true;
+ }
+ else
+ {
+ session = TelemetryService.DefaultSession;
+ }
+
+ s_telemetrySession = session;
+ return new MSBuildActivitySource(session);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void Dispose()
+ {
+ if (s_ownsSession && s_telemetrySession is TelemetrySession session)
+ {
+ session.Dispose();
+ }
+
+ s_telemetrySession = null;
+ }
+ }
+#endif
+}
diff --git a/src/Framework/Telemetry/VSTelemetryActivity.cs b/src/Framework/Telemetry/VSTelemetryActivity.cs
new file mode 100644
index 00000000000..f9f21374d1b
--- /dev/null
+++ b/src/Framework/Telemetry/VSTelemetryActivity.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if NETFRAMEWORK
+
+using System.Collections.Generic;
+using Microsoft.VisualStudio.Telemetry;
+
+namespace Microsoft.Build.Framework.Telemetry
+{
+ ///
+ /// Represents a Visual Studio telemetry activity that wraps a .
+ /// This class provides an implementation of for the VS Telemetry system,
+ /// allowing telemetry data to be collected and sent when running on .NET Framework.
+ ///
+ internal class VsTelemetryActivity : IActivity
+ {
+ private readonly TelemetryScope _scope;
+ private TelemetryResult _result = TelemetryResult.Success;
+
+ private bool _disposed;
+
+ public VsTelemetryActivity(TelemetryScope scope)
+ {
+ _scope = scope;
+ }
+
+ public IActivity? SetTags(IActivityTelemetryDataHolder? dataHolder)
+ {
+ Dictionary? tags = dataHolder?.GetActivityProperties();
+
+ if (tags != null)
+ {
+ foreach (KeyValuePair tag in tags)
+ {
+ _ = SetTag(tag.Key, tag.Value);
+ }
+ }
+
+ return this;
+ }
+
+ public IActivity? SetTag(string key, object? value)
+ {
+ if (value != null)
+ {
+ _scope.EndEvent.Properties[$"{TelemetryConstants.PropertyPrefix}{key}"] = new TelemetryComplexProperty(value);
+ }
+
+ return this;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _scope.End(_result);
+ _disposed = true;
+ }
+ }
+}
+
+#endif
diff --git a/src/Framework/Traits.cs b/src/Framework/Traits.cs
index 8cbf21feef1..d02e95ce944 100644
--- a/src/Framework/Traits.cs
+++ b/src/Framework/Traits.cs
@@ -154,14 +154,12 @@ public Traits()
///
public bool SdkTelemetryOptOut = IsEnvVarOneOrTrue("DOTNET_CLI_TELEMETRY_OPTOUT");
public bool FrameworkTelemetryOptOut = IsEnvVarOneOrTrue("MSBUILD_TELEMETRY_OPTOUT");
- public double? TelemetrySampleRateOverride = ParseDoubleFromEnvironmentVariable("MSBUILD_TELEMETRY_SAMPLE_RATE");
public bool ExcludeTasksDetailsFromTelemetry = IsEnvVarOneOrTrue("MSBUILDTELEMETRYEXCLUDETASKSDETAILS");
public bool FlushNodesTelemetryIntoConsole = IsEnvVarOneOrTrue("MSBUILDFLUSHNODESTELEMETRYINTOCONSOLE");
public bool EnableTargetOutputLogging = IsEnvVarOneOrTrue("MSBUILDTARGETOUTPUTLOGGING");
// for VS17.14
- public readonly bool TelemetryOptIn = IsEnvVarOneOrTrue("MSBUILD_TELEMETRY_OPTIN");
public readonly bool SlnParsingWithSolutionPersistenceOptIn = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILD_PARSE_SLN_WITH_SOLUTIONPERSISTENCE"));
public static void UpdateFromEnvironment()
@@ -180,19 +178,6 @@ private static int ParseIntFromEnvironmentVariableOrDefault(string environmentVa
: defaultValue;
}
- ///
- /// Parse a double from an environment variable with invariant culture.
- ///
- private static double? ParseDoubleFromEnvironmentVariable(string environmentVariable)
- {
- return double.TryParse(Environment.GetEnvironmentVariable(environmentVariable),
- NumberStyles.Float,
- CultureInfo.InvariantCulture,
- out double result)
- ? result
- : null;
- }
-
internal static bool IsEnvVarOneOrTrue(string name)
{
string? value = Environment.GetEnvironmentVariable(name);
diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs
index 0bc9bd7aafd..b5ad7405d7e 100644
--- a/src/MSBuild/XMake.cs
+++ b/src/MSBuild/XMake.cs
@@ -248,9 +248,9 @@ string[] args
DebuggerLaunchCheck();
// Initialize new build telemetry and record start of this build.
- KnownTelemetry.PartialBuildTelemetry = new BuildTelemetry { StartAt = DateTime.UtcNow };
- // Initialize OpenTelemetry infrastructure
- OpenTelemetryManager.Instance.Initialize(isStandalone: true);
+ KnownTelemetry.PartialBuildTelemetry = new BuildTelemetry { StartAt = DateTime.UtcNow, IsStandaloneExecution = true };
+
+ TelemetryManager.Instance?.Initialize(isStandalone: true);
using PerformanceLogEventListener eventListener = PerformanceLogEventListener.Create();
@@ -298,12 +298,12 @@ string[] args
{
DumpCounters(false /* log to console */);
}
- OpenTelemetryManager.Instance.Shutdown();
+
+ TelemetryManager.Instance?.Dispose();
return exitCode;
}
-
///
/// Returns true if arguments allows or make sense to leverage msbuild server.
///
diff --git a/src/MSBuild/app.amd64.config b/src/MSBuild/app.amd64.config
index 9bf8b014e38..00194107526 100644
--- a/src/MSBuild/app.amd64.config
+++ b/src/MSBuild/app.amd64.config
@@ -57,16 +57,18 @@
-
-
+
+
-
-
+
+
+
-
-
+
+
+
@@ -104,90 +106,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/MSBuild/app.config b/src/MSBuild/app.config
index 9c41d0b862c..6f2cba28e6c 100644
--- a/src/MSBuild/app.config
+++ b/src/MSBuild/app.config
@@ -39,10 +39,6 @@
-
-
-
-
@@ -72,10 +68,6 @@
-
-
-
-
diff --git a/src/Package/MSBuild.VSSetup/files.swr b/src/Package/MSBuild.VSSetup/files.swr
index 3b75caa7fb1..4e091c37e8e 100644
--- a/src/Package/MSBuild.VSSetup/files.swr
+++ b/src/Package/MSBuild.VSSetup/files.swr
@@ -41,7 +41,6 @@ folder InstallDir:\MSBuild\Current\Bin
file source=$(X86BinPath)Microsoft.VisualStudio.SolutionPersistence.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
file source=$(X86BinPath)RuntimeContracts.dll
file source=$(X86BinPath)System.Buffers.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=2
- file source=$(X86BinPath)System.Diagnostics.DiagnosticSource.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
file source=$(X86BinPath)System.Formats.Nrbf.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=2
file source=$(X86BinPath)System.IO.Pipelines.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=2
file source=$(X86BinPath)System.Memory.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=2
@@ -89,24 +88,7 @@ folder InstallDir:\MSBuild\Current\Bin
file source=$(X86BinPath)Microsoft.ServiceModel.targets
file source=$(X86BinPath)Microsoft.WinFx.targets
file source=$(X86BinPath)Microsoft.WorkflowBuildExtensions.targets
- file source=$(X86BinPath)Microsoft.VisualStudio.OpenTelemetry.ClientExtensions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.VisualStudio.OpenTelemetry.Collector.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
file source=$(X86BinPath)Microsoft.VisualStudio.Utilities.Internal.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)OpenTelemetry.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)OpenTelemetry.Api.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)OpenTelemetry.Api.ProviderBuilderExtensions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Configuration.Abstractions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Configuration.Binder.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Configuration.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.DependencyInjection.Abstractions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.DependencyInjection.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Logging.Abstractions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Logging.Configuration.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Logging.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Options.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Options.ConfigurationExtensions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Primitives.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
- file source=$(X86BinPath)Microsoft.Extensions.Diagnostics.Abstractions.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=3
file source=$(X86BinPath)Newtonsoft.Json.dll vs.file.ngenApplications="[installDir]\MSBuild\Current\Bin\amd64\MSBuild.exe" vs.file.ngenArchitecture=all vs.file.ngenPriority=2
folder InstallDir:\MSBuild\Current\Bin\MSBuild
diff --git a/src/Package/Microsoft.Build.UnGAC/Program.cs b/src/Package/Microsoft.Build.UnGAC/Program.cs
index a13f518146d..d686da3dc75 100644
--- a/src/Package/Microsoft.Build.UnGAC/Program.cs
+++ b/src/Package/Microsoft.Build.UnGAC/Program.cs
@@ -32,8 +32,6 @@ private static void Main(string[] args)
"BuildXL.Utilities.Core, Version=1.0.0.0",
"BuildXL.Native, Version=1.0.0.0",
"Microsoft.VisualStudio.SolutionPersistence, Version=1.0.0.0",
- "Microsoft.VisualStudio.OpenTelemetry.ClientExtensions, Version=0.1.0.0",
- "Microsoft.VisualStudio.OpenTelemetry.Collector, Version=0.1.0.0",
};
uint hresult = NativeMethods.CreateAssemblyCache(out IAssemblyCache assemblyCache, 0);