Skip to content

Commit f69319c

Browse files
Introduce TaskParameterEventArgs (#6155)
Instead of just logging a BuildMessageEventArgs with a list of all items and metadata concatenated into a large string (often 5 MB in size or more) it keeps a structured representation of items and metadata. TaskParameterEventArgs inherits from BuildMessageEventArgs and the Message implementation materializes the large string on demand. However when only the BinaryLogger is present the Message is never accessed, thus saving on allocations. The Message is also never sent across the nodes nor written into the binlog. TaskParameterEventArgs is instantiated in 5 locations: ItemGroup Include and Remove inside targets, task inputs, and two cases for task outputs. Storing smaller strings in the binlog results in very significant savings from string deduplication. A 22 MB binlog goes down to 3.5 MB in size. We're also seeing build speed improvements from 33 seconds to 30 seconds. Significant reduction in memory allocations since we no longer need to allocate the large strings and send them across the nodes.
1 parent 63087cd commit f69319c

32 files changed

+1371
-284
lines changed

ref/Microsoft.Build.Framework/net/Microsoft.Build.Framework.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ protected BuildEventArgs(string message, string helpKeyword, string senderName,
3838
public Microsoft.Build.Framework.BuildEventContext BuildEventContext { get { throw null; } set { } }
3939
public string HelpKeyword { get { throw null; } }
4040
public virtual string Message { get { throw null; } protected set { } }
41+
protected System.DateTime RawTimestamp { get { throw null; } set { } }
4142
public string SenderName { get { throw null; } }
4243
public int ThreadId { get { throw null; } }
4344
public System.DateTime Timestamp { get { throw null; } }
@@ -589,6 +590,22 @@ public TaskFinishedEventArgs(string message, string helpKeyword, string projectF
589590
public string TaskName { get { throw null; } }
590591
}
591592
public delegate void TaskFinishedEventHandler(object sender, Microsoft.Build.Framework.TaskFinishedEventArgs e);
593+
public partial class TaskParameterEventArgs : Microsoft.Build.Framework.BuildMessageEventArgs
594+
{
595+
public TaskParameterEventArgs(Microsoft.Build.Framework.TaskParameterMessageKind kind, string itemType, System.Collections.IList items, bool logItemMetadata, System.DateTime eventTimestamp) { }
596+
public System.Collections.IList Items { get { throw null; } }
597+
public string ItemType { get { throw null; } }
598+
public Microsoft.Build.Framework.TaskParameterMessageKind Kind { get { throw null; } }
599+
public bool LogItemMetadata { get { throw null; } }
600+
public override string Message { get { throw null; } }
601+
}
602+
public enum TaskParameterMessageKind
603+
{
604+
TaskInput = 0,
605+
TaskOutput = 1,
606+
AddItem = 2,
607+
RemoveItem = 3,
608+
}
592609
public partial class TaskPropertyInfo
593610
{
594611
public TaskPropertyInfo(string name, System.Type typeOfParameter, bool output, bool required) { }

ref/Microsoft.Build.Framework/netstandard/Microsoft.Build.Framework.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ protected BuildEventArgs(string message, string helpKeyword, string senderName,
3838
public Microsoft.Build.Framework.BuildEventContext BuildEventContext { get { throw null; } set { } }
3939
public string HelpKeyword { get { throw null; } }
4040
public virtual string Message { get { throw null; } protected set { } }
41+
protected System.DateTime RawTimestamp { get { throw null; } set { } }
4142
public string SenderName { get { throw null; } }
4243
public int ThreadId { get { throw null; } }
4344
public System.DateTime Timestamp { get { throw null; } }
@@ -588,6 +589,22 @@ public TaskFinishedEventArgs(string message, string helpKeyword, string projectF
588589
public string TaskName { get { throw null; } }
589590
}
590591
public delegate void TaskFinishedEventHandler(object sender, Microsoft.Build.Framework.TaskFinishedEventArgs e);
592+
public partial class TaskParameterEventArgs : Microsoft.Build.Framework.BuildMessageEventArgs
593+
{
594+
public TaskParameterEventArgs(Microsoft.Build.Framework.TaskParameterMessageKind kind, string itemType, System.Collections.IList items, bool logItemMetadata, System.DateTime eventTimestamp) { }
595+
public System.Collections.IList Items { get { throw null; } }
596+
public string ItemType { get { throw null; } }
597+
public Microsoft.Build.Framework.TaskParameterMessageKind Kind { get { throw null; } }
598+
public bool LogItemMetadata { get { throw null; } }
599+
public override string Message { get { throw null; } }
600+
}
601+
public enum TaskParameterMessageKind
602+
{
603+
TaskInput = 0,
604+
TaskOutput = 1,
605+
AddItem = 2,
606+
RemoveItem = 3,
607+
}
591608
public partial class TaskPropertyInfo
592609
{
593610
public TaskPropertyInfo(string name, System.Type typeOfParameter, bool output, bool required) { }

src/Build.UnitTests/BackEnd/EventSourceSink_Tests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public void ConsumeEventsGoodEventsNoHandlers()
6868
eventHelper.RaiseBuildEvent(RaiseEventHelper.NormalMessage);
6969
eventHelper.RaiseBuildEvent(RaiseEventHelper.TaskFinished);
7070
eventHelper.RaiseBuildEvent(RaiseEventHelper.CommandLine);
71+
eventHelper.RaiseBuildEvent(RaiseEventHelper.TaskParameter);
7172
eventHelper.RaiseBuildEvent(RaiseEventHelper.Warning);
7273
eventHelper.RaiseBuildEvent(RaiseEventHelper.Error);
7374
eventHelper.RaiseBuildEvent(RaiseEventHelper.TargetStarted);
@@ -99,6 +100,7 @@ public void LoggerExceptionInEventHandler()
99100
RaiseExceptionInEventHandler(RaiseEventHelper.NormalMessage, exception);
100101
RaiseExceptionInEventHandler(RaiseEventHelper.TaskFinished, exception);
101102
RaiseExceptionInEventHandler(RaiseEventHelper.CommandLine, exception);
103+
RaiseExceptionInEventHandler(RaiseEventHelper.TaskParameter, exception);
102104
RaiseExceptionInEventHandler(RaiseEventHelper.Warning, exception);
103105
RaiseExceptionInEventHandler(RaiseEventHelper.Error, exception);
104106
RaiseExceptionInEventHandler(RaiseEventHelper.TargetStarted, exception);
@@ -733,6 +735,11 @@ internal class RaiseEventHelper
733735
/// </summary>
734736
private static TaskCommandLineEventArgs s_taskCommandLine = new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low);
735737

738+
/// <summary>
739+
/// Task Parameter Event
740+
/// </summary>
741+
private static TaskParameterEventArgs s_taskParameter = new TaskParameterEventArgs(TaskParameterMessageKind.TaskInput, "ItemName", null, true, DateTime.MinValue);
742+
736743
/// <summary>
737744
/// Build Warning Event
738745
/// </summary>
@@ -883,6 +890,11 @@ internal static TaskCommandLineEventArgs CommandLine
883890
}
884891
}
885892

893+
/// <summary>
894+
/// Event which can be raised in multiple tests.
895+
/// </summary>
896+
internal static TaskParameterEventArgs TaskParameter => s_taskParameter;
897+
886898
/// <summary>
887899
/// Event which can be raised in multiple tests.
888900
/// </summary>

src/Build.UnitTests/BackEnd/NodePackets_Tests.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System;
5+
using System.Linq;
56
using Microsoft.Build.Framework;
67
using Microsoft.Build.BackEnd;
78
using Microsoft.Build.Shared;
@@ -44,6 +45,7 @@ public void VerifyEventType()
4445
TaskStartedEventArgs taskStarted = new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName");
4546
TaskFinishedEventArgs taskFinished = new TaskFinishedEventArgs("message", "help", "projectFile", "taskFile", "taskName", true);
4647
TaskCommandLineEventArgs commandLine = new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low);
48+
TaskParameterEventArgs taskParameter = CreateTaskParameter();
4749
BuildWarningEventArgs warning = new BuildWarningEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
4850
BuildErrorEventArgs error = new BuildErrorEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender");
4951
TargetStartedEventArgs targetStarted = new TargetStartedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile");
@@ -58,6 +60,7 @@ public void VerifyEventType()
5860
VerifyLoggingPacket(taskStarted, LoggingEventType.TaskStartedEvent);
5961
VerifyLoggingPacket(taskFinished, LoggingEventType.TaskFinishedEvent);
6062
VerifyLoggingPacket(commandLine, LoggingEventType.TaskCommandLineEvent);
63+
VerifyLoggingPacket(taskParameter, LoggingEventType.TaskParameterEvent);
6164
VerifyLoggingPacket(warning, LoggingEventType.BuildWarningEvent);
6265
VerifyLoggingPacket(error, LoggingEventType.BuildErrorEvent);
6366
VerifyLoggingPacket(targetStarted, LoggingEventType.TargetStartedEvent);
@@ -67,12 +70,41 @@ public void VerifyEventType()
6770
VerifyLoggingPacket(externalStartedEvent, LoggingEventType.CustomEvent);
6871
}
6972

73+
private static TaskParameterEventArgs CreateTaskParameter()
74+
{
75+
var items = new TaskItemData[]
76+
{
77+
new TaskItemData("ItemSpec1", null),
78+
new TaskItemData("ItemSpec2", Enumerable.Range(1,3).ToDictionary(i => i.ToString(), i => i.ToString() + "value"))
79+
};
80+
var result = new TaskParameterEventArgs(
81+
TaskParameterMessageKind.TaskInput,
82+
"ItemName",
83+
items,
84+
logItemMetadata: true,
85+
DateTime.MinValue);
86+
87+
// normalize line endings as we can't rely on the line endings of NodePackets_Tests.cs
88+
Assert.Equal(@"Task Parameter:
89+
ItemName=
90+
ItemSpec1
91+
ItemSpec2
92+
1=1value
93+
2=2value
94+
3=3value".Replace("\r\n", "\n"), result.Message);
95+
96+
return result;
97+
}
98+
7099
/// <summary>
71100
/// Tests serialization of LogMessagePacket with each kind of event type.
72101
/// </summary>
73102
[Fact]
74103
public void TestTranslation()
75104
{
105+
// need to touch the type so that the static constructor runs
106+
_ = ItemGroupLoggingHelper.OutputItemParameterMessagePrefix;
107+
76108
TaskItem item = new TaskItem("Hello", "my.proj");
77109
List<TaskItem> targetOutputs = new List<TaskItem>();
78110
targetOutputs.Add(item);
@@ -88,6 +120,7 @@ public void TestTranslation()
88120
new TaskStartedEventArgs("message", "help", "projectFile", "taskFile", "taskName"),
89121
new TaskFinishedEventArgs("message", "help", "projectFile", "taskFile", "taskName", true),
90122
new TaskCommandLineEventArgs("commandLine", "taskName", MessageImportance.Low),
123+
CreateTaskParameter(),
91124
new BuildWarningEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender"),
92125
new BuildErrorEventArgs("SubCategoryForSchemaValidationErrors", "MSB4000", "file", 1, 2, 3, 4, "message", "help", "sender"),
93126
new TargetStartedEventArgs("message", "help", "targetName", "ProjectFile", "targetFile"),
@@ -281,6 +314,19 @@ private void CompareLogMessagePackets(LogMessagePacket left, LogMessagePacket ri
281314
Assert.Equal(leftCommand.TaskName, rightCommand.TaskName);
282315
break;
283316

317+
case LoggingEventType.TaskParameterEvent:
318+
var leftTaskParameter = left.NodeBuildEvent.Value.Value as TaskParameterEventArgs;
319+
var rightTaskParameter = right.NodeBuildEvent.Value.Value as TaskParameterEventArgs;
320+
Assert.NotNull(leftTaskParameter);
321+
Assert.NotNull(rightTaskParameter);
322+
Assert.Equal(leftTaskParameter.Kind, rightTaskParameter.Kind);
323+
Assert.Equal(leftTaskParameter.ItemType, rightTaskParameter.ItemType);
324+
Assert.Equal(leftTaskParameter.Items.Count, rightTaskParameter.Items.Count);
325+
Assert.Equal(leftTaskParameter.Message, rightTaskParameter.Message);
326+
Assert.Equal(leftTaskParameter.BuildEventContext, rightTaskParameter.BuildEventContext);
327+
Assert.Equal(leftTaskParameter.Timestamp, rightTaskParameter.Timestamp);
328+
break;
329+
284330
case LoggingEventType.TaskFinishedEvent:
285331
TaskFinishedEventArgs leftTaskFinished = left.NodeBuildEvent.Value.Value as TaskFinishedEventArgs;
286332
TaskFinishedEventArgs rightTaskFinished = right.NodeBuildEvent.Value.Value as TaskFinishedEventArgs;

src/Build.UnitTests/BinaryLogger_Tests.cs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace Microsoft.Build.UnitTests
77
{
88
public class BinaryLoggerTests : IDisposable
99
{
10-
private static string s_testProject = @"
10+
private const string s_testProject = @"
1111
<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
1212
<PropertyGroup>
1313
<TestProperty>Test</TestProperty>
@@ -22,6 +22,38 @@ public class BinaryLoggerTests : IDisposable
2222
<Exec Command='echo a'/>
2323
</Target>
2424
</Project>";
25+
26+
private const string s_testProject2 = @"
27+
<Project>
28+
<ItemGroup>
29+
<Compile Include=""0.cs"" />
30+
</ItemGroup>
31+
<ItemDefinitionGroup>
32+
<Compile>
33+
<MetadataFromItemDefinition>fromItemDefinition%61%62%63&lt;&gt;</MetadataFromItemDefinition>
34+
</Compile>
35+
</ItemDefinitionGroup>
36+
<Target Name=""Build"" Outputs=""@(CombinedOutput)"">
37+
<ItemGroup>
38+
<Compile Include=""1.cs"">
39+
<MetadataName>MetadataValue1%61%62%63&lt;&gt;</MetadataName>
40+
</Compile>
41+
<Compile Remove=""1.cs"" />
42+
<Compile Include=""2.cs"" />
43+
<Compile Include=""3.cs"">
44+
<CustomMetadata>custom%61%62%63&lt;&gt;</CustomMetadata>
45+
</Compile>
46+
</ItemGroup>
47+
<Message Importance=""High"" Condition=""$(Test) != true"" Text=""Hello"" />
48+
<CombinePath BasePath=""base"" Paths=""@(Compile)"">
49+
<Output TaskParameter=""CombinedPaths"" ItemName=""CombinedOutput""/>
50+
</CombinePath>
51+
<ItemGroup>
52+
<Compile Remove=""2.cs"" />
53+
</ItemGroup>
54+
</Target>
55+
</Project>";
56+
2557
private readonly TestEnvironment _env;
2658
private string _logFile;
2759

@@ -35,8 +67,10 @@ public BinaryLoggerTests(ITestOutputHelper output)
3567
_logFile = _env.ExpectFile(".binlog").Path;
3668
}
3769

38-
[Fact]
39-
public void TestBinaryLoggerRoundtrip()
70+
[Theory]
71+
[InlineData(s_testProject)]
72+
[InlineData(s_testProject2)]
73+
public void TestBinaryLoggerRoundtrip(string projectText)
4074
{
4175
var binaryLogger = new BinaryLogger();
4276

@@ -45,14 +79,14 @@ public void TestBinaryLoggerRoundtrip()
4579
var mockLogFromBuild = new MockLogger();
4680

4781
// build and log into binary logger and mockLogger1
48-
ObjectModelHelpers.BuildProjectExpectSuccess(s_testProject, binaryLogger, mockLogFromBuild);
82+
ObjectModelHelpers.BuildProjectExpectSuccess(projectText, binaryLogger, mockLogFromBuild);
4983

5084
var mockLogFromPlayback = new MockLogger();
5185

5286
var binaryLogReader = new BinaryLogReplayEventSource();
5387
mockLogFromPlayback.Initialize(binaryLogReader);
5488

55-
// read the binary log and replay into mockLogger2testassembly
89+
// read the binary log and replay into mockLogger2
5690
binaryLogReader.Replay(_logFile);
5791

5892
// the binlog will have more information than recorded by the text log

src/Build.UnitTests/BuildEventArgsSerialization_Tests.cs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.IO;
55
using System.Linq;
66
using System.Text;
7+
using Microsoft.Build.BackEnd;
78
using Microsoft.Build.Framework;
89
using Microsoft.Build.Framework.Profiler;
910
using Microsoft.Build.Logging;
@@ -70,21 +71,39 @@ public void RoundtripProjectStartedEventArgs()
7071
toolsVersion: "Current");
7172
args.BuildEventContext = new BuildEventContext(1, 2, 3, 4, 5, 6);
7273

73-
Roundtrip(args,
74+
Roundtrip<ProjectStartedEventArgs>(args,
7475
e => ToString(e.BuildEventContext),
7576
e => ToString(e.GlobalProperties),
76-
e => ToString(e.Items.OfType<DictionaryEntry>().ToDictionary(d => d.Key.ToString(), d => ((ITaskItem)d.Value).ItemSpec)),
77+
e => GetItemsString(e.Items),
7778
e => e.Message,
7879
e => ToString(e.ParentProjectBuildEventContext),
7980
e => e.ProjectFile,
8081
e => e.ProjectId.ToString(),
81-
e => ToString(e.Properties.OfType<DictionaryEntry>().ToDictionary(d => d.Key.ToString(), d => d.Value.ToString())),
82+
e => ToString(e.Properties.OfType<DictionaryEntry>().ToDictionary((Func<DictionaryEntry, string>)(d => d.Key.ToString()), (Func<DictionaryEntry, string>)(d => d.Value.ToString()))),
8283
e => e.TargetNames,
8384
e => e.ThreadId.ToString(),
8485
e => e.Timestamp.ToString(),
8586
e => e.ToolsVersion);
8687
}
8788

89+
private string GetItemsString(IEnumerable items)
90+
{
91+
return ToString(items.OfType<DictionaryEntry>().ToDictionary(d => d.Key.ToString(), d => GetTaskItemString((ITaskItem)d.Value)));
92+
}
93+
94+
private string GetTaskItemString(ITaskItem taskItem)
95+
{
96+
var sb = new StringBuilder();
97+
sb.Append(taskItem.ItemSpec);
98+
foreach (string name in taskItem.MetadataNames)
99+
{
100+
var value = taskItem.GetMetadata(name);
101+
sb.Append($";{name}={value}");
102+
}
103+
104+
return sb.ToString();
105+
}
106+
88107
[Fact]
89108
public void RoundtripProjectFinishedEventArgs()
90109
{
@@ -309,6 +328,23 @@ public void RoundtripTaskCommandLineEventArgs()
309328
e => e.Subcategory);
310329
}
311330

331+
[Fact]
332+
public void RoundtripTaskParameterEventArgs()
333+
{
334+
var items = new TaskItemData[]
335+
{
336+
new TaskItemData("ItemSpec1", null),
337+
new TaskItemData("ItemSpec2", Enumerable.Range(1,3).ToDictionary(i => i.ToString(), i => i.ToString() + "value"))
338+
};
339+
var args = new TaskParameterEventArgs(TaskParameterMessageKind.TaskOutput, "ItemName", items, true, DateTime.MinValue);
340+
341+
Roundtrip(args,
342+
e => e.Kind.ToString(),
343+
e => e.ItemType,
344+
e => e.LogItemMetadata.ToString(),
345+
e => GetItemsString(e.Items));
346+
}
347+
312348
[Fact]
313349
public void RoundtripProjectEvaluationStartedEventArgs()
314350
{

0 commit comments

Comments
 (0)