1
- // Licensed to the .NET Foundation under one or more agreements.
1
+ // Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
4
using System . Globalization ;
@@ -13,7 +13,7 @@ public sealed class MSBuildLogger : INodeLogger
13
13
{
14
14
private readonly IFirstTimeUseNoticeSentinel _sentinel =
15
15
new FirstTimeUseNoticeSentinel ( ) ;
16
- private readonly ITelemetry ? _telemetry = null ;
16
+ private readonly ITelemetry ? _telemetry ;
17
17
18
18
internal const string TargetFrameworkTelemetryEventName = "targetframeworkeval" ;
19
19
internal const string BuildTelemetryEventName = "build" ;
@@ -22,6 +22,10 @@ public sealed class MSBuildLogger : INodeLogger
22
22
internal const string BuildcheckRunEventName = "buildcheck/run" ;
23
23
internal const string BuildcheckRuleStatsEventName = "buildcheck/rule" ;
24
24
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
+
25
29
internal const string SdkTaskBaseCatchExceptionTelemetryEventName = "taskBaseCatchException" ;
26
30
internal const string PublishPropertiesTelemetryEventName = "PublishProperties" ;
27
31
internal const string WorkloadPublishPropertiesTelemetryEventName = "WorkloadPublishProperties" ;
@@ -48,6 +52,15 @@ public sealed class MSBuildLogger : INodeLogger
48
52
/// </summary>
49
53
internal const string SdkContainerPublishErrorEventName = "sdk/container/publish/error" ;
50
54
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
+
51
64
public MSBuildLogger ( )
52
65
{
53
66
try
@@ -73,6 +86,14 @@ public MSBuildLogger()
73
86
}
74
87
}
75
88
89
+ /// <summary>
90
+ /// Constructor for testing purposes.
91
+ /// </summary>
92
+ internal MSBuildLogger ( ITelemetry telemetry )
93
+ {
94
+ _telemetry = telemetry ;
95
+ }
96
+
76
97
public void Initialize ( IEventSource eventSource , int nodeCount )
77
98
{
78
99
Initialize ( eventSource ) ;
@@ -95,6 +116,8 @@ public void Initialize(IEventSource eventSource)
95
116
{
96
117
eventSource2 . TelemetryLogged += OnTelemetryLogged ;
97
118
}
119
+
120
+ eventSource . BuildFinished += OnBuildFinished ;
98
121
}
99
122
}
100
123
catch ( Exception )
@@ -103,37 +126,103 @@ public void Initialize(IEventSource eventSource)
103
126
}
104
127
}
105
128
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
+
106
193
internal static void FormatAndSend ( ITelemetry ? telemetry , TelemetryEventArgs args )
107
194
{
108
195
switch ( args . EventName )
109
196
{
110
197
case TargetFrameworkTelemetryEventName :
111
- TrackEvent ( telemetry , $ "msbuild/{ TargetFrameworkTelemetryEventName } ", args . Properties , [ ] , [ ] ) ;
198
+ TrackEvent ( telemetry , $ "msbuild/{ TargetFrameworkTelemetryEventName } ", args . Properties ) ;
112
199
break ;
113
200
case BuildTelemetryEventName :
114
201
TrackEvent ( telemetry , $ "msbuild/{ BuildTelemetryEventName } ", args . Properties ,
115
202
toBeHashed : [ "ProjectPath" , "BuildTarget" ] ,
116
- toBeMeasured : [ "BuildDurationInMilliseconds" , "InnerBuildDurationInMilliseconds" ] ) ;
203
+ toBeMeasured : [ "BuildDurationInMilliseconds" , "InnerBuildDurationInMilliseconds" ]
204
+ ) ;
117
205
break ;
118
206
case LoggingConfigurationTelemetryEventName :
119
207
TrackEvent ( telemetry , $ "msbuild/{ LoggingConfigurationTelemetryEventName } ", args . Properties ,
120
- toBeHashed : [ ] ,
121
- toBeMeasured : [ "FileLoggersCount" ] ) ;
208
+ toBeMeasured : [ "FileLoggersCount" ]
209
+ ) ;
122
210
break ;
123
211
case BuildcheckAcquisitionFailureEventName :
124
212
TrackEvent ( telemetry , $ "msbuild/{ BuildcheckAcquisitionFailureEventName } ", args . Properties ,
125
- toBeHashed : [ "AssemblyName" , "ExceptionType" , "ExceptionMessage" ] ,
126
- toBeMeasured : [ ] ) ;
213
+ toBeHashed : [ "AssemblyName" , "ExceptionType" , "ExceptionMessage" ]
214
+ ) ;
127
215
break ;
128
216
case BuildcheckRunEventName :
129
217
TrackEvent ( telemetry , $ "msbuild/{ BuildcheckRunEventName } ", args . Properties ,
130
- toBeHashed : [ ] ,
131
- toBeMeasured : [ "TotalRuntimeInMilliseconds" ] ) ;
218
+ toBeMeasured : [ "TotalRuntimeInMilliseconds" ]
219
+ ) ;
132
220
break ;
133
221
case BuildcheckRuleStatsEventName :
134
222
TrackEvent ( telemetry , $ "msbuild/{ BuildcheckRuleStatsEventName } ", args . Properties ,
135
223
toBeHashed : [ "RuleId" , "CheckFriendlyName" ] ,
136
- toBeMeasured : [ "TotalRuntimeInMilliseconds" ] ) ;
224
+ toBeMeasured : [ "TotalRuntimeInMilliseconds" ]
225
+ ) ;
137
226
break ;
138
227
// Pass through events that don't need special handling
139
228
case SdkTaskBaseCatchExceptionTelemetryEventName :
@@ -143,15 +232,15 @@ internal static void FormatAndSend(ITelemetry? telemetry, TelemetryEventArgs arg
143
232
case SdkContainerPublishBaseImageInferenceEventName :
144
233
case SdkContainerPublishSuccessEventName :
145
234
case SdkContainerPublishErrorEventName :
146
- TrackEvent ( telemetry , args . EventName , args . Properties , [ ] , [ ] ) ;
235
+ TrackEvent ( telemetry , args . EventName , args . Properties ) ;
147
236
break ;
148
237
default :
149
238
// Ignore unknown events
150
239
break ;
151
240
}
152
241
}
153
242
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 )
155
244
{
156
245
if ( telemetry == null || ! telemetry . Enabled )
157
246
{
@@ -168,7 +257,7 @@ private static void TrackEvent(ITelemetry? telemetry, string eventName, IDiction
168
257
if ( eventProperties . TryGetValue ( propertyToBeHashed , out var value ) )
169
258
{
170
259
// Lets lazy allocate in case there is tons of telemetry
171
- properties ??= new Dictionary < string , string ? > ( eventProperties ) ;
260
+ properties ??= new ( eventProperties ) ;
172
261
properties [ propertyToBeHashed ] = Sha256Hasher . HashWithNormalizedCasing ( value ! ) ;
173
262
}
174
263
}
@@ -178,10 +267,10 @@ private static void TrackEvent(ITelemetry? telemetry, string eventName, IDiction
178
267
{
179
268
foreach ( var propertyToBeMeasured in toBeMeasured )
180
269
{
181
- if ( eventProperties . TryGetValue ( propertyToBeMeasured , out string ? value ) )
270
+ if ( eventProperties . TryGetValue ( propertyToBeMeasured , out var value ) )
182
271
{
183
272
// Lets lazy allocate in case there is tons of telemetry
184
- properties ??= new Dictionary < string , string ? > ( eventProperties ) ;
273
+ properties ??= new ( eventProperties ) ;
185
274
properties . Remove ( propertyToBeMeasured ) ;
186
275
if ( double . TryParse ( value , CultureInfo . InvariantCulture , out double realValue ) )
187
276
{
@@ -198,7 +287,14 @@ private static void TrackEvent(ITelemetry? telemetry, string eventName, IDiction
198
287
199
288
private void OnTelemetryLogged ( object sender , TelemetryEventArgs args )
200
289
{
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
+ }
202
298
}
203
299
204
300
public void Shutdown ( )
@@ -214,5 +310,6 @@ public void Shutdown()
214
310
}
215
311
216
312
public LoggerVerbosity Verbosity { get ; set ; }
313
+
217
314
public string ? Parameters { get ; set ; }
218
315
}
0 commit comments