|
41 | 41 | '$(Scenario)' == 'WasmTestOnChrome' or |
42 | 42 | '$(Scenario)' == 'WasmTestOnFirefox'">true</IsRunningLibraryTests> |
43 | 43 |
|
| 44 | + <WasmBatchLibraryTests Condition="'$(WasmBatchLibraryTests)' == '' and '$(RuntimeFlavor)' == 'CoreCLR' and '$(Scenario)' == 'WasmTestOnChrome'">true</WasmBatchLibraryTests> |
| 45 | + <WasmBatchLibraryTests Condition="'$(WasmBatchLibraryTests)' == ''">false</WasmBatchLibraryTests> |
| 46 | + <_WasmBatchLargeThreshold Condition="'$(_WasmBatchLargeThreshold)' == ''">52428800</_WasmBatchLargeThreshold> |
| 47 | + |
44 | 48 | <HelixExtensionTargets /> |
45 | 49 | <PrepareForBuildHelixWorkItems_WasmDependsOn> |
46 | 50 | PrepareHelixCorrelationPayload_Wasm; |
47 | 51 | _AddWorkItemsForLibraryTests; |
| 52 | + _AddBatchedWorkItemsForLibraryTests; |
48 | 53 | _AddWorkItemsForBuildWasmApps |
49 | 54 | </PrepareForBuildHelixWorkItems_WasmDependsOn> |
50 | 55 |
|
|
172 | 177 |
|
173 | 178 | <Import Project="$(RepositoryEngineeringDir)testing\wasm-provisioning.targets" /> |
174 | 179 |
|
| 180 | + <UsingTask TaskName="_GroupWorkItems" TaskFactory="RoslynCodeTaskFactory" |
| 181 | + AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> |
| 182 | + <ParameterGroup> |
| 183 | + <Items ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> |
| 184 | + <BatchSize ParameterType="System.Int32" Required="false" /> |
| 185 | + <LargeThreshold ParameterType="System.Int64" Required="false" /> |
| 186 | + <GroupedItems ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> |
| 187 | + </ParameterGroup> |
| 188 | + <Task> |
| 189 | + <Using Namespace="System" /> |
| 190 | + <Using Namespace="System.Collections.Generic" /> |
| 191 | + <Using Namespace="System.IO" /> |
| 192 | + <Using Namespace="System.Linq" /> |
| 193 | + <Code Type="Fragment" Language="cs"> |
| 194 | +<![CDATA[ |
| 195 | +if (BatchSize <= 0) BatchSize = 10; |
| 196 | +if (LargeThreshold <= 0) LargeThreshold = 52428800L; // 50MB |
| 197 | +
|
| 198 | +var itemsWithSize = new List<(ITaskItem item, long size)>(); |
| 199 | +foreach (var item in Items) |
| 200 | +{ |
| 201 | + long size = 0; |
| 202 | + if (File.Exists(item.ItemSpec)) |
| 203 | + { |
| 204 | + size = new FileInfo(item.ItemSpec).Length; |
| 205 | + } |
| 206 | + itemsWithSize.Add((item, size)); |
| 207 | +} |
| 208 | +
|
| 209 | +// Sort largest first for greedy bin-packing |
| 210 | +itemsWithSize.Sort((a, b) => b.size.CompareTo(a.size)); |
| 211 | +
|
| 212 | +var result = new List<ITaskItem>(); |
| 213 | +int negativeBatchId = -1; |
| 214 | +
|
| 215 | +// Separate large items (each gets its own batch) |
| 216 | +var smallItems = new List<(ITaskItem item, long size)>(); |
| 217 | +foreach (var entry in itemsWithSize) |
| 218 | +{ |
| 219 | + if (entry.size > LargeThreshold) |
| 220 | + { |
| 221 | + var newItem = new TaskItem(entry.item); |
| 222 | + newItem.SetMetadata("BatchId", negativeBatchId.ToString()); |
| 223 | + negativeBatchId--; |
| 224 | + result.Add(newItem); |
| 225 | + } |
| 226 | + else |
| 227 | + { |
| 228 | + smallItems.Add(entry); |
| 229 | + } |
| 230 | +} |
| 231 | +
|
| 232 | +// Greedy bin-packing for small items |
| 233 | +if (smallItems.Count > 0) |
| 234 | +{ |
| 235 | + int numBatches = Math.Min(BatchSize, smallItems.Count); |
| 236 | + var batchSizes = new long[numBatches]; |
| 237 | + var batchAssignments = new List<ITaskItem>[numBatches]; |
| 238 | + for (int i = 0; i < numBatches; i++) |
| 239 | + batchAssignments[i] = new List<ITaskItem>(); |
| 240 | +
|
| 241 | + foreach (var entry in smallItems) |
| 242 | + { |
| 243 | + // Find batch with smallest total size |
| 244 | + int minIdx = 0; |
| 245 | + for (int i = 1; i < numBatches; i++) |
| 246 | + { |
| 247 | + if (batchSizes[i] < batchSizes[minIdx]) |
| 248 | + minIdx = i; |
| 249 | + } |
| 250 | + batchSizes[minIdx] += entry.size; |
| 251 | + var newItem = new TaskItem(entry.item); |
| 252 | + newItem.SetMetadata("BatchId", minIdx.ToString()); |
| 253 | + batchAssignments[minIdx].Add(newItem); |
| 254 | + } |
| 255 | +
|
| 256 | + for (int i = 0; i < numBatches; i++) |
| 257 | + result.AddRange(batchAssignments[i]); |
| 258 | +} |
| 259 | +
|
| 260 | +GroupedItems = result.ToArray(); |
| 261 | +]]> |
| 262 | + </Code> |
| 263 | + </Task> |
| 264 | + </UsingTask> |
| 265 | + |
| 266 | + <UsingTask TaskName="_ComputeBatchTimeout" TaskFactory="RoslynCodeTaskFactory" |
| 267 | + AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> |
| 268 | + <ParameterGroup> |
| 269 | + <GroupedItems ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> |
| 270 | + <BatchIds ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> |
| 271 | + <ItemPrefix ParameterType="System.String" Required="true" /> |
| 272 | + <BatchOutputDir ParameterType="System.String" Required="true" /> |
| 273 | + <TimedItems ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" /> |
| 274 | + </ParameterGroup> |
| 275 | + <Task> |
| 276 | + <Using Namespace="System" /> |
| 277 | + <Using Namespace="System.Collections.Generic" /> |
| 278 | + <Code Type="Fragment" Language="cs"> |
| 279 | +<![CDATA[ |
| 280 | +var counts = new Dictionary<string, int>(); |
| 281 | +foreach (var item in GroupedItems) |
| 282 | +{ |
| 283 | + string bid = item.GetMetadata("BatchId"); |
| 284 | + if (!counts.ContainsKey(bid)) counts[bid] = 0; |
| 285 | + counts[bid]++; |
| 286 | +} |
| 287 | +
|
| 288 | +var result = new List<ITaskItem>(); |
| 289 | +foreach (var batchId in BatchIds) |
| 290 | +{ |
| 291 | + string bid = batchId.ItemSpec; |
| 292 | + int count = counts.ContainsKey(bid) ? counts[bid] : 1; |
| 293 | + int totalMinutes = Math.Max(10, count * 2); |
| 294 | + var ts = TimeSpan.FromMinutes(totalMinutes); |
| 295 | +
|
| 296 | + var helixItem = new TaskItem(ItemPrefix + "Batch-" + bid); |
| 297 | + helixItem.SetMetadata("PayloadDirectory", BatchOutputDir + "batch-" + bid + "/"); |
| 298 | + helixItem.SetMetadata("Command", "chmod +x WasmBatchRunner.sh && ./WasmBatchRunner.sh"); |
| 299 | + helixItem.SetMetadata("Timeout", ts.ToString(@"hh\:mm\:ss")); |
| 300 | + result.Add(helixItem); |
| 301 | +} |
| 302 | +
|
| 303 | +TimedItems = result.ToArray(); |
| 304 | +]]> |
| 305 | + </Code> |
| 306 | + </Task> |
| 307 | + </UsingTask> |
| 308 | + |
175 | 309 | <Target Name="PrepareHelixCorrelationPayload_Wasm"> |
176 | 310 | <Error Condition="'$(Scenario)' != 'WasmTestOnV8' and |
177 | 311 | '$(Scenario)' != 'WasmTestOnChrome' and |
|
228 | 362 | <Target Name="PrepareForBuildHelixWorkItems_Wasm" |
229 | 363 | DependsOnTargets="$(PrepareForBuildHelixWorkItems_WasmDependsOn);$(HelixExtensionTargets)" /> |
230 | 364 |
|
231 | | - <Target Name="_AddWorkItemsForLibraryTests" Condition="'$(IsRunningLibraryTests)' == 'true'"> |
| 365 | + <Target Name="_AddWorkItemsForLibraryTests" Condition="'$(IsRunningLibraryTests)' == 'true' and '$(WasmBatchLibraryTests)' != 'true'"> |
232 | 366 | <ItemGroup Label="Add samples"> |
233 | 367 | <_WasmWorkItem Include="$(TestArchiveRoot)browseronly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome' or '$(Scenario)' == 'WasmTestOnFirefox'" /> |
234 | 368 | <_WasmWorkItem Include="$(TestArchiveRoot)chromeonly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome'" /> |
|
273 | 407 |
|
274 | 408 | </ItemGroup> |
275 | 409 | </Target> |
| 410 | + |
| 411 | + <Target Name="_AddBatchedWorkItemsForLibraryTests" |
| 412 | + Condition="'$(IsRunningLibraryTests)' == 'true' and '$(WasmBatchLibraryTests)' == 'true'"> |
| 413 | + |
| 414 | + <!-- Collect all test ZIPs the same way the non-batched path does --> |
| 415 | + <ItemGroup> |
| 416 | + <_WasmBatchWorkItem Include="$(TestArchiveRoot)browseronly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome' or '$(Scenario)' == 'WasmTestOnFirefox'" /> |
| 417 | + <_WasmBatchWorkItem Include="$(TestArchiveRoot)chromeonly/**/*.zip" Condition="'$(Scenario)' == 'WasmTestOnChrome'" /> |
| 418 | + </ItemGroup> |
| 419 | + |
| 420 | + <!-- Sample apps have different runners (no RunTests.sh), keep them as individual work items --> |
| 421 | + <ItemGroup> |
| 422 | + <_WasmBatchSampleZip Condition="'$(Scenario)' == 'WasmTestOnV8'" Include="$(TestArchiveRoot)runonly/**/*.Console.V8.*.Sample.zip" /> |
| 423 | + <_WasmBatchSampleZip Condition="'$(Scenario)' == 'WasmTestOnChrome'" Include="$(TestArchiveRoot)runonly/**/*.Browser.*.Sample.zip" /> |
| 424 | + |
| 425 | + <HelixWorkItem Include="@(_WasmBatchSampleZip -> '$(WorkItemPrefix)%(FileName)')"> |
| 426 | + <PayloadArchive>%(Identity)</PayloadArchive> |
| 427 | + <Command>$(HelixCommand)</Command> |
| 428 | + <Timeout>$(_workItemTimeout)</Timeout> |
| 429 | + </HelixWorkItem> |
| 430 | + </ItemGroup> |
| 431 | + |
| 432 | + <ItemGroup> |
| 433 | + <_WasmBatchDefaultItems Include="$(WorkItemArchiveWildCard)" Exclude="$(HelixCorrelationPayload)" /> |
| 434 | + </ItemGroup> |
| 435 | + |
| 436 | + <!-- Batch only test suites (not sample apps) --> |
| 437 | + <ItemGroup> |
| 438 | + <_WasmBatchAllItems Include="@(_WasmBatchWorkItem)" /> |
| 439 | + <_WasmBatchAllItems Include="@(_WasmBatchDefaultItems)" /> |
| 440 | + </ItemGroup> |
| 441 | + |
| 442 | + <!-- Assign batch IDs via greedy bin-packing --> |
| 443 | + <_GroupWorkItems Items="@(_WasmBatchAllItems)" BatchSize="20" LargeThreshold="$(_WasmBatchLargeThreshold)"> |
| 444 | + <Output TaskParameter="GroupedItems" ItemName="_WasmGroupedItem" /> |
| 445 | + </_GroupWorkItems> |
| 446 | + |
| 447 | + <!-- Determine unique batch IDs --> |
| 448 | + <ItemGroup> |
| 449 | + <_WasmBatchId Include="@(_WasmGroupedItem -> '%(BatchId)')" /> |
| 450 | + <_WasmUniqueBatchId Include="@(_WasmBatchId->Distinct())" /> |
| 451 | + </ItemGroup> |
| 452 | + |
| 453 | + <!-- Stage each batch: copy ZIPs and the runner script into a per-batch directory --> |
| 454 | + <MakeDir Directories="$(IntermediateOutputPath)helix-batches/batch-%(_WasmUniqueBatchId.Identity)/" /> |
| 455 | + <Copy SourceFiles="@(_WasmGroupedItem)" DestinationFolder="$(IntermediateOutputPath)helix-batches/batch-%(BatchId)/" /> |
| 456 | + <Copy SourceFiles="$(RepositoryEngineeringDir)testing/WasmBatchRunner.sh" |
| 457 | + DestinationFolder="$(IntermediateOutputPath)helix-batches/batch-%(_WasmUniqueBatchId.Identity)/" /> |
| 458 | + |
| 459 | + <!-- Compute per-batch timeout: 2 minutes per suite, minimum 10 minutes --> |
| 460 | + <_ComputeBatchTimeout GroupedItems="@(_WasmGroupedItem)" BatchIds="@(_WasmUniqueBatchId)" |
| 461 | + ItemPrefix="$(WorkItemPrefix)" BatchOutputDir="$(IntermediateOutputPath)helix-batches/"> |
| 462 | + <Output TaskParameter="TimedItems" ItemName="_WasmTimedBatchItem" /> |
| 463 | + </_ComputeBatchTimeout> |
| 464 | + |
| 465 | + <ItemGroup> |
| 466 | + <HelixWorkItem Include="@(_WasmTimedBatchItem)" /> |
| 467 | + </ItemGroup> |
| 468 | + </Target> |
276 | 469 | </Project> |
0 commit comments