Skip to content

Commit d9308af

Browse files
authored
Pick rc2 fixes for msbuild telemetry and sdkresolver to GA (#51090)
2 parents 35066e5 + 766a85b commit d9308af

File tree

7 files changed

+226
-27
lines changed

7 files changed

+226
-27
lines changed

src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingLogger.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ public void Initialize(IEventSource eventSource)
2626
eventSource4.IncludeEvaluationPropertiesAndItems();
2727
}
2828

29-
// Only forward telemetry events
29+
// Forward telemetry events
3030
if (eventSource is IEventSource2 eventSource2)
3131
{
3232
eventSource2.TelemetryLogged += (sender, args) => BuildEventRedirector.ForwardEvent(args);
3333
}
34+
35+
// Forward build finished events. Is used for logging the aggregated build events.
36+
eventSource.BuildFinished += (sender, args) => BuildEventRedirector.ForwardEvent(args);
3437
}
3538

3639
public void Initialize(IEventSource eventSource, int nodeCount)

src/Cli/dotnet/Commands/MSBuild/MSBuildLogger.cs

Lines changed: 114 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Globalization;
@@ -13,7 +13,7 @@ public sealed class MSBuildLogger : INodeLogger
1313
{
1414
private readonly IFirstTimeUseNoticeSentinel _sentinel =
1515
new FirstTimeUseNoticeSentinel();
16-
private readonly ITelemetry? _telemetry = null;
16+
private readonly ITelemetry? _telemetry;
1717

1818
internal const string TargetFrameworkTelemetryEventName = "targetframeworkeval";
1919
internal const string BuildTelemetryEventName = "build";
@@ -22,6 +22,10 @@ public sealed class MSBuildLogger : INodeLogger
2222
internal const string BuildcheckRunEventName = "buildcheck/run";
2323
internal const string BuildcheckRuleStatsEventName = "buildcheck/rule";
2424

25+
// These two events are aggregated and sent at the end of the build.
26+
internal const string TaskFactoryTelemetryAggregatedEventName = "build/tasks/taskfactory";
27+
internal const string TasksTelemetryAggregatedEventName = "build/tasks";
28+
2529
internal const string SdkTaskBaseCatchExceptionTelemetryEventName = "taskBaseCatchException";
2630
internal const string PublishPropertiesTelemetryEventName = "PublishProperties";
2731
internal const string WorkloadPublishPropertiesTelemetryEventName = "WorkloadPublishProperties";
@@ -48,6 +52,15 @@ public sealed class MSBuildLogger : INodeLogger
4852
/// </summary>
4953
internal const string SdkContainerPublishErrorEventName = "sdk/container/publish/error";
5054

55+
/// <summary>
56+
/// Stores aggregated telemetry data by event name and property name.
57+
/// </summary>
58+
/// <remarks>
59+
/// Key: event name, Value: property name to aggregated count.
60+
/// Aggregation is very basic. Only integer properties are aggregated by summing values. Non-integer properties are ignored.
61+
/// </remarks>
62+
private Dictionary<string, Dictionary<string, int>> _aggregatedEvents = new();
63+
5164
public MSBuildLogger()
5265
{
5366
try
@@ -73,6 +86,14 @@ public MSBuildLogger()
7386
}
7487
}
7588

89+
/// <summary>
90+
/// Constructor for testing purposes.
91+
/// </summary>
92+
internal MSBuildLogger(ITelemetry telemetry)
93+
{
94+
_telemetry = telemetry;
95+
}
96+
7697
public void Initialize(IEventSource eventSource, int nodeCount)
7798
{
7899
Initialize(eventSource);
@@ -95,6 +116,8 @@ public void Initialize(IEventSource eventSource)
95116
{
96117
eventSource2.TelemetryLogged += OnTelemetryLogged;
97118
}
119+
120+
eventSource.BuildFinished += OnBuildFinished;
98121
}
99122
}
100123
catch (Exception)
@@ -103,37 +126,103 @@ public void Initialize(IEventSource eventSource)
103126
}
104127
}
105128

129+
private void OnBuildFinished(object sender, BuildFinishedEventArgs e)
130+
{
131+
SendAggregatedEventsOnBuildFinished(_telemetry);
132+
}
133+
134+
internal void SendAggregatedEventsOnBuildFinished(ITelemetry? telemetry)
135+
{
136+
if (_aggregatedEvents.TryGetValue(TaskFactoryTelemetryAggregatedEventName, out var taskFactoryData))
137+
{
138+
var taskFactoryProperties = ConvertToStringDictionary(taskFactoryData);
139+
140+
TrackEvent(telemetry, $"msbuild/{TaskFactoryTelemetryAggregatedEventName}", taskFactoryProperties, toBeHashed: [], toBeMeasured: []);
141+
_aggregatedEvents.Remove(TaskFactoryTelemetryAggregatedEventName);
142+
}
143+
144+
if (_aggregatedEvents.TryGetValue(TasksTelemetryAggregatedEventName, out var tasksData))
145+
{
146+
var tasksProperties = ConvertToStringDictionary(tasksData);
147+
148+
TrackEvent(telemetry, $"msbuild/{TasksTelemetryAggregatedEventName}", tasksProperties, toBeHashed: [], toBeMeasured: []);
149+
_aggregatedEvents.Remove(TasksTelemetryAggregatedEventName);
150+
}
151+
}
152+
153+
private static Dictionary<string, string?> ConvertToStringDictionary(Dictionary<string, int> properties)
154+
{
155+
Dictionary<string, string?> stringProperties = new();
156+
foreach (var kvp in properties)
157+
{
158+
stringProperties[kvp.Key] = kvp.Value.ToString(CultureInfo.InvariantCulture);
159+
}
160+
161+
return stringProperties;
162+
}
163+
164+
internal void AggregateEvent(TelemetryEventArgs args)
165+
{
166+
if (args.EventName is null)
167+
{
168+
return;
169+
}
170+
171+
if (!_aggregatedEvents.TryGetValue(args.EventName, out var eventData))
172+
{
173+
eventData = [];
174+
_aggregatedEvents[args.EventName] = eventData;
175+
}
176+
177+
foreach (var kvp in args.Properties)
178+
{
179+
if (int.TryParse(kvp.Value, CultureInfo.InvariantCulture, out int count))
180+
{
181+
if (!eventData.ContainsKey(kvp.Key))
182+
{
183+
eventData[kvp.Key] = count;
184+
}
185+
else
186+
{
187+
eventData[kvp.Key] += count;
188+
}
189+
}
190+
}
191+
}
192+
106193
internal static void FormatAndSend(ITelemetry? telemetry, TelemetryEventArgs args)
107194
{
108195
switch (args.EventName)
109196
{
110197
case TargetFrameworkTelemetryEventName:
111-
TrackEvent(telemetry, $"msbuild/{TargetFrameworkTelemetryEventName}", args.Properties, [], []);
198+
TrackEvent(telemetry, $"msbuild/{TargetFrameworkTelemetryEventName}", args.Properties);
112199
break;
113200
case BuildTelemetryEventName:
114201
TrackEvent(telemetry, $"msbuild/{BuildTelemetryEventName}", args.Properties,
115202
toBeHashed: ["ProjectPath", "BuildTarget"],
116-
toBeMeasured: ["BuildDurationInMilliseconds", "InnerBuildDurationInMilliseconds"]);
203+
toBeMeasured: ["BuildDurationInMilliseconds", "InnerBuildDurationInMilliseconds"]
204+
);
117205
break;
118206
case LoggingConfigurationTelemetryEventName:
119207
TrackEvent(telemetry, $"msbuild/{LoggingConfigurationTelemetryEventName}", args.Properties,
120-
toBeHashed: [],
121-
toBeMeasured: ["FileLoggersCount"]);
208+
toBeMeasured: ["FileLoggersCount"]
209+
);
122210
break;
123211
case BuildcheckAcquisitionFailureEventName:
124212
TrackEvent(telemetry, $"msbuild/{BuildcheckAcquisitionFailureEventName}", args.Properties,
125-
toBeHashed: ["AssemblyName", "ExceptionType", "ExceptionMessage"],
126-
toBeMeasured: []);
213+
toBeHashed: ["AssemblyName", "ExceptionType", "ExceptionMessage"]
214+
);
127215
break;
128216
case BuildcheckRunEventName:
129217
TrackEvent(telemetry, $"msbuild/{BuildcheckRunEventName}", args.Properties,
130-
toBeHashed: [],
131-
toBeMeasured: ["TotalRuntimeInMilliseconds"]);
218+
toBeMeasured: ["TotalRuntimeInMilliseconds"]
219+
);
132220
break;
133221
case BuildcheckRuleStatsEventName:
134222
TrackEvent(telemetry, $"msbuild/{BuildcheckRuleStatsEventName}", args.Properties,
135223
toBeHashed: ["RuleId", "CheckFriendlyName"],
136-
toBeMeasured: ["TotalRuntimeInMilliseconds"]);
224+
toBeMeasured: ["TotalRuntimeInMilliseconds"]
225+
);
137226
break;
138227
// Pass through events that don't need special handling
139228
case SdkTaskBaseCatchExceptionTelemetryEventName:
@@ -143,15 +232,15 @@ internal static void FormatAndSend(ITelemetry? telemetry, TelemetryEventArgs arg
143232
case SdkContainerPublishBaseImageInferenceEventName:
144233
case SdkContainerPublishSuccessEventName:
145234
case SdkContainerPublishErrorEventName:
146-
TrackEvent(telemetry, args.EventName, args.Properties, [], []);
235+
TrackEvent(telemetry, args.EventName, args.Properties);
147236
break;
148237
default:
149238
// Ignore unknown events
150239
break;
151240
}
152241
}
153242

154-
private static void TrackEvent(ITelemetry? telemetry, string eventName, IDictionary<string, string?> eventProperties, string[]? toBeHashed, string[]? toBeMeasured)
243+
private static void TrackEvent(ITelemetry? telemetry, string eventName, IDictionary<string, string?> eventProperties, string[]? toBeHashed = null, string[]? toBeMeasured = null)
155244
{
156245
if (telemetry == null || !telemetry.Enabled)
157246
{
@@ -168,7 +257,7 @@ private static void TrackEvent(ITelemetry? telemetry, string eventName, IDiction
168257
if (eventProperties.TryGetValue(propertyToBeHashed, out var value))
169258
{
170259
// Lets lazy allocate in case there is tons of telemetry
171-
properties ??= new Dictionary<string, string?>(eventProperties);
260+
properties ??= new(eventProperties);
172261
properties[propertyToBeHashed] = Sha256Hasher.HashWithNormalizedCasing(value!);
173262
}
174263
}
@@ -178,10 +267,10 @@ private static void TrackEvent(ITelemetry? telemetry, string eventName, IDiction
178267
{
179268
foreach (var propertyToBeMeasured in toBeMeasured)
180269
{
181-
if (eventProperties.TryGetValue(propertyToBeMeasured, out string? value))
270+
if (eventProperties.TryGetValue(propertyToBeMeasured, out var value))
182271
{
183272
// Lets lazy allocate in case there is tons of telemetry
184-
properties ??= new Dictionary<string, string?>(eventProperties);
273+
properties ??= new(eventProperties);
185274
properties.Remove(propertyToBeMeasured);
186275
if (double.TryParse(value, CultureInfo.InvariantCulture, out double realValue))
187276
{
@@ -198,7 +287,14 @@ private static void TrackEvent(ITelemetry? telemetry, string eventName, IDiction
198287

199288
private void OnTelemetryLogged(object sender, TelemetryEventArgs args)
200289
{
201-
FormatAndSend(_telemetry, args);
290+
if (args.EventName == TaskFactoryTelemetryAggregatedEventName || args.EventName == TasksTelemetryAggregatedEventName)
291+
{
292+
AggregateEvent(args);
293+
}
294+
else
295+
{
296+
FormatAndSend(_telemetry, args);
297+
}
202298
}
203299

204300
public void Shutdown()
@@ -214,5 +310,6 @@ public void Shutdown()
214310
}
215311

216312
public LoggerVerbosity Verbosity { get; set; }
313+
217314
public string? Parameters { get; set; }
218315
}

src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/MSBuildSdkResolver.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public sealed class DotNetMSBuildSdkResolver : SdkResolver
3232
private readonly Func<string, string, string?> _getMsbuildRuntime;
3333
private readonly NETCoreSdkResolver _netCoreSdkResolver;
3434

35-
private const string DOTNET_HOST = nameof(DOTNET_HOST);
35+
private const string DOTNET_HOST_PATH = nameof(DOTNET_HOST_PATH);
3636
private const string DotnetHostExperimentalKey = "DOTNET_EXPERIMENTAL_HOST_PATH";
3737
private const string MSBuildTaskHostRuntimeVersion = "SdkResolverMSBuildTaskHostRuntimeVersion";
3838
private const string SdkResolverHonoredGlobalJson = "SdkResolverHonoredGlobalJson";
@@ -245,12 +245,12 @@ private sealed class CachedState
245245
// this is the future-facing implementation.
246246
environmentVariablesToAdd ??= new Dictionary<string, string?>(1)
247247
{
248-
[DOTNET_HOST] = fullPathToMuxer
248+
[DOTNET_HOST_PATH] = fullPathToMuxer
249249
};
250250
}
251251
else
252252
{
253-
logger?.LogMessage($"Could not set '{DOTNET_HOST}' environment variable because dotnet executable '{fullPathToMuxer}' does not exist.");
253+
logger?.LogMessage($"Could not set '{DOTNET_HOST_PATH}' environment variable because dotnet executable '{fullPathToMuxer}' does not exist.");
254254
}
255255

256256
string? runtimeVersion = dotnetRoot != null ?

test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,8 @@ private void OverrideKnownILCompilerPackRuntimeIdentifiers(XDocument project, st
546546
project.Root.Add(new XElement(ns + "ItemGroup",
547547
new XElement(ns + "KnownILCompilerPack",
548548
new XAttribute("Update", "@(KnownILCompilerPack)"),
549-
new XElement(ns + "ILCompilerRuntimeIdentifiers", runtimeIdentifiers))));
549+
new XElement(ns + "ILCompilerRuntimeIdentifiers", runtimeIdentifiers),
550+
new XElement(ns + "ILCompilerPortableRuntimeIdentifiers", runtimeIdentifiers))));
550551
}
551552

552553
[RequiresMSBuildVersionTheory("17.0.0.32901")]

test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishIncrementally.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public void It_cleans_between_single_file_publishes()
134134
CheckPublishOutput(publishDir, expectedSingleExeFiles.Append(testProject.Name + ".dll"), null);
135135
}
136136

137-
[Fact]
137+
[Fact(Skip = "https://github.com/dotnet/sdk/issues/50784")]
138138
public void It_cleans_before_trimmed_single_file_publish()
139139
{
140140
var testProject = new TestProject()

test/dotnet.Tests/CommandTests/MSBuild/FakeTelemetry.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ public class FakeTelemetry : ITelemetry
1111
{
1212
public bool Enabled { get; set; } = true;
1313

14+
private readonly List<LogEntry> _logEntries = new List<LogEntry>();
15+
1416
public void TrackEvent(string eventName, IDictionary<string, string> properties, IDictionary<string, double> measurements)
1517
{
16-
LogEntry = new LogEntry { EventName = eventName, Properties = properties, Measurement = measurements };
17-
18+
var entry = new LogEntry { EventName = eventName, Properties = properties, Measurement = measurements };
19+
_logEntries.Add(entry);
1820
}
1921

2022
public void Flush()
@@ -25,8 +27,8 @@ public void Dispose()
2527
{
2628
}
2729

28-
public LogEntry LogEntry { get; private set; }
30+
public LogEntry LogEntry => _logEntries.Count > 0 ? _logEntries[_logEntries.Count - 1] : null;
2931

32+
public IReadOnlyList<LogEntry> LogEntries => _logEntries.AsReadOnly();
3033
}
31-
3234
}

0 commit comments

Comments
 (0)