microsoft / microsoft/testfx

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

Open
#11,382 0 comments 0 reactions 0 assignees View on GitHub
agentic-workflows area/performance type/automation
Dominant language
C#
Stars
1k
Forks
312
Avg merge
8h 30m
Merged PRs (30d)
469

Description

> [!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](https://github.com/microsoft/testfx/compare/main...efficiency/azuredevops-single-pass-min-max-7b36f35ab3d68d7c?expand=1&title=%5Befficiency-improver%5D%20Single-pass%20min%2Fmax%20for%20Azure%20DevOps%20result%20date%20aggregation)**
>
> The original pull request description is below.

---

## Goal and rationale

`AzureDevOpsResultIdStore.GetEarliestStartedDate`/`GetLatestCompletedDate` (both the public `IReadOnlyList` overload and the internal `IReadOnlyList` 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 **Settings** → **Actions** → **General** and enable **Allow GitHub Actions to create and approve pull requests**. See also: [gh-aw FAQ](https://github.github.com/gh-aw/reference/faq/#why-is-my-create-pull-request-workflow-failing-with-github-actions-is-not-permitted-to-create-or-approve-pull-requests)

Show patch preview (30 of 103 lines)

```diff
From d0a24adf94ca306775e1977d3f4b938017c5b42c Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 00b4e2a445a42b8768fdd706e7de778e582fb034
From: "github-actions[bot]"
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 overload and the
internal IReadOnlyList 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 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:
>
> ```yaml
> network:
> allowed:
> - defaults
> - "southcentralus0.in.applicationinsights.azure.com"
> ```
>
> See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information.
>
>

> 🤖 **Automated content by GitHub Copilot.** Generated by the [Efficiency Improver](https://github.com/microsoft/testfx/actions/runs/35397880978/agentic_workflow) workflow. · copilot · auto · 279.7 AIC · ⌖ 18.4 AIC · ⊞ 17.8K · [◷]( · [◷](https://github.com/search?q=repo%3Amicrosoft%2Ftestfx+%22gh-aw-workflow-id%3A+efficiency-improver%22&type=pullrequests))
>

Add this agentic workflow to your repo

To install this agentic workflow, run

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

Contributor guide

Open the contributing guide

Research direction

Start in src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsResultIdStore.cs and AzureDevOpsResultIdStore.AttemptHistory.cs, focusing on the date aggregation methods and the adjacent SumDurations pattern. Run the AzureDevOpsLivePublishingTests-filtered suite and verify that null dates, all-null inputs, and existing publishing behavior remain covered while the implementation uses the stated single-pass approach.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
performance, testing
Issue type
Refactor
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.