Skip to content

[efficiency-improver] Single-pass min/max for Azure DevOps result date aggregation #11382

Description

@github-actions

Tip

Your pull request is ready to create! 🎉 ✅

Everything is OK—the changes have been pushed to branch efficiency/azuredevops-single-pass-min-max-7b36f35ab3d68d7c. Please review the changes, including any protected files, before creating the pull request.

Create the pull request

The original pull request description is below.


Goal and rationale

AzureDevOpsResultIdStore.GetEarliestStartedDate/GetLatestCompletedDate (both the public IReadOnlyList<AzureDevOpsTestCaseResult> overload and the internal IReadOnlyList<AzureDevOpsTestSubResult> overload used for attempt history) used attempts.Where(a => a.X is not null).Min/Max(a => a.X). These two methods are always called together from the Azure DevOps live-publishing flush path (AzureDevOpsTestResultsPublisher.ResultPublishing.cs, AzureDevOpsTestResultsPublisher.AttemptPublishing.cs, AzureDevOpsResultIdStore.cs/.Persistence.cs) — i.e. once per batch of published test results/attempts, not a one-time cold path. Each call allocated a Where iterator and enumerated the list twice (once for Min, once for Max).

Focus area

Code-Level Efficiency (redundant enumeration / avoidable LINQ overhead in a per-batch-flush hot path).

Approach

Replaced both LINQ chains with manual single-pass foreach loops that track the running min/max directly — mirroring the existing hand-rolled SumDurations/AddDurations pattern already used right next to these methods in the same file. No behavioral change: null-date entries are still skipped, and an all-null list still returns null.

Energy efficiency evidence

Proxy metrics used: CPU time (Stopwatch) and allocated bytes (GC.GetAllocatedBytesForCurrentThread()) — both map directly to CPU/DRAM energy draw per Green Software Foundation guidance on hardware efficiency.

Standalone (not committed) console benchmark, .NET 8 Release, 2,000,000 call-pairs (GetEarliestStartedDate + GetLatestCompletedDate) over a 5-element list mimicking a typical attempt/sub-result batch:

Time Allocated
OLD (Where().Min()/Max()) 1073.37 ms 192,000,040 B
NEW (single-pass loop) 511.95 ms 128,000,040 B

~2.1x faster, ~33% less allocation for this call-pair shape. The remaining allocation in both versions comes from DateTimeOffset? boxing in the benchmark harness itself, not from the methods under test.

Green Software Foundation context

  • Hardware Efficiency: fewer CPU cycles per flush batch and less GC pressure from eliminated iterator allocations.
  • SCI: reduces the Energy term of the SCI equation for the "publish one batch of Azure DevOps test results" functional unit, aggregated across every CI run that uses --report-azdo.

Trade-offs

The loop form is a few lines longer than the one-line LINQ expression, but it directly mirrors the existing SumDurations helper's style already present in the same file, so it doesn't introduce a new pattern to the codebase.

Reproducibility

Benchmark harness (not committed) computed old vs. new implementations side-by-side with warmup iterations before each measured loop; happy to reproduce on request.

Test Status

  • ./build.sh (full repo): 0 warnings/errors.
  • dotnet build test/UnitTests/Microsoft.Testing.Extensions.UnitTests -f net8.0: 0 warnings/errors.
  • Full Microsoft.Testing.Extensions.UnitTests suite (net8.0, run directly): 1839/1839 passed (37 pre-existing skips).
  • AzureDevOpsLivePublishingTests-filtered run: 154/154 passed.
  • dotnet format whitespace --verify-no-changes on both changed files: clean.

No public API signature changes — both public methods keep their exact signatures, only the internal implementation changed.


Note

GitHub Actions is not permitted to create or approve pull requests in this repository.

The changes have been pushed to branch efficiency/azuredevops-single-pass-min-max-7b36f35ab3d68d7c and are ready to review.

To fix the permissions issue, go to SettingsActionsGeneral and enable Allow GitHub Actions to create and approve pull requests. See also: gh-aw FAQ

Show patch preview (30 of 103 lines)
From d0a24adf94ca306775e1977d3f4b938017c5b42c Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 00b4e2a445a42b8768fdd706e7de778e582fb034
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Fri, 18 Sep 2026 21:53:47 +0000
Subject: [PATCH] Single-pass min/max for Azure DevOps result date aggregation

Replace Where().Min()/Where().Max() LINQ chains with manual single-pass
loops in AzureDevOpsResultIdStore's GetEarliestStartedDate/GetLatestCompletedDate
(both the public IReadOnlyList<AzureDevOpsTestCaseResult> overload and the
internal IReadOnlyList<AzureDevOpsTestSubResult> overload). These are called
in pairs from the result-flush hot path (once per batch of published test
results/attempts), so the double enumeration + Where iterator allocation was
avoided in favor of the same manual-accumulation pattern already used by the
adjacent SumDurations helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 ...AzureDevOpsResultIdStore.AttemptHistory.cs | 26 +++++++++++++++++--
 .../AzureDevOpsResultIdStore.cs               | 26 +++++++++++++++++--
 2 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs
index 3ae5eda..a03f7e7 100644
--- a/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs
+++ b/src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.AttemptHistory.cs
@@ -59,10 +59,32 @@ or AzureDevOpsLivePublishingConstants.NotExecutedTestOutcome
                 : right.Value > long.MaxValue - left.Value ? long.MaxValue : left.Value + right.Value;
 
     private static DateTimeOffset? GetEarliestStartedDate(IReadOnlyList<AzureDevOpsTestSubResult> attempts)
-        => attempts.Where(attempt => attempt.StartedDate is not null).Min(a
... (truncated)

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • southcentralus0.in.applicationinsights.azure.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "southcentralus0.in.applicationinsights.azure.com"

See Network Configuration for more information.

🤖 Automated content by GitHub Copilot. Generated by the Efficiency Improver workflow. · copilot · auto · 279.7 AIC · ⌖ 18.4 AIC · ⊞ 17.8K · [◷]( · )

Add this agentic workflow to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/efficiency-improver.md@main

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions