perf: eliminate List<string> allocation in BuildProviderSummary

BuildProviderSummary is called on every gateway usage-status update.
The previous implementation allocated a List<string> (up to 3 entries)
and called string.Join, producing a heap-allocated list wrapper plus
join enumeration in addition to the final string.

With at most 2 provider slots + an optional overflow suffix, the
combinations fit into two nullable string variables and a switch
expression, producing only the final string allocation.

Also adds 9 unit tests covering all branch paths:
empty providers, single-provider with usage/error/no-windows,
two providers, three providers (overflow), missing display name,
all-empty providers, and overflow with one valid provider.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
github-actions[bot] 2026-04-15 01:19:19 +00:00 committed by GitHub
parent e46dfe7830
commit d33e873edf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 207 additions and 15 deletions

View File

@ -1885,34 +1885,47 @@ public class OpenClawGatewayClient : WebSocketClientBase
{
if (status.Providers.Count == 0) return "";
var parts = new List<string>();
// At most 2 providers are shown; track them with two nullable strings to avoid
// allocating a List<string> on every usage-status update.
string? p0 = null, p1 = null;
int included = 0;
foreach (var provider in status.Providers)
{
if (parts.Count == 2) break;
if (included == 2) break;
var displayName = string.IsNullOrWhiteSpace(provider.DisplayName) ? provider.Provider : provider.DisplayName;
if (string.IsNullOrWhiteSpace(displayName))
displayName = "provider";
string part;
if (!string.IsNullOrWhiteSpace(provider.Error))
{
parts.Add($"{displayName}: error");
continue;
part = $"{displayName}: error";
}
else
{
if (provider.Windows.Count == 0) continue;
var window = provider.Windows.MaxBy(w => w.UsedPercent);
if (window is null) continue;
var remaining = Math.Clamp((int)Math.Round(100 - window.UsedPercent), 0, 100);
part = $"{displayName}: {remaining}% left";
}
if (provider.Windows.Count == 0) continue;
var window = provider.Windows.MaxBy(w => w.UsedPercent);
if (window is null) continue;
var remaining = Math.Clamp((int)Math.Round(100 - window.UsedPercent), 0, 100);
parts.Add($"{displayName}: {remaining}% left");
if (included == 0) p0 = part;
else p1 = part;
included++;
}
if (parts.Count == 0)
return "";
if (included == 0) return "";
if (status.Providers.Count > 2)
parts.Add($"+{status.Providers.Count - 2}");
return string.Join(" · ", parts);
string? overflow = status.Providers.Count > 2 ? $"+{status.Providers.Count - 2}" : null;
return (p1, overflow) switch
{
(null, null) => p0!,
(null, _) => $"{p0} · {overflow}",
(_, null) => $"{p0} · {p1}",
_ => $"{p0} · {p1} · {overflow}",
};
}
private static string? FirstNonEmpty(params string?[] values)

View File

@ -110,6 +110,14 @@ public class OpenClawGatewayClientTests
return GetUsageState();
}
public string CallBuildProviderSummary(GatewayUsageStatusInfo status)
{
var method = typeof(OpenClawGatewayClient).GetMethod(
"BuildProviderSummary",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
return (string)method!.Invoke(null, new object[] { status })!;
}
public GatewayUsageInfo ParseUsageCostPayload(string payloadJson)
{
InvokePrivatePayloadParser("ParseUsageCost", payloadJson);
@ -644,6 +652,177 @@ public class OpenClawGatewayClientTests
Assert.Contains("left", usage.ProviderSummary!);
}
// ── BuildProviderSummary tests ──────────────────────────────────────────────
[Fact]
public void BuildProviderSummary_NoProviders_ReturnsEmpty()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo { Providers = [] };
Assert.Equal("", helper.CallBuildProviderSummary(status));
}
[Fact]
public void BuildProviderSummary_SingleProviderWithUsage_ShowsRemainingPercent()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo
{
DisplayName = "OpenAI",
Windows = [new GatewayUsageWindowInfo { Label = "daily", UsedPercent = 25.0 }]
}
]
};
var result = helper.CallBuildProviderSummary(status);
Assert.Equal("OpenAI: 75% left", result);
}
[Fact]
public void BuildProviderSummary_SingleProviderWithError_ShowsErrorLabel()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo { DisplayName = "Anthropic", Error = "rate limited" }
]
};
Assert.Equal("Anthropic: error", helper.CallBuildProviderSummary(status));
}
[Fact]
public void BuildProviderSummary_ProviderWithNoWindows_IsSkipped()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers = [new GatewayUsageProviderInfo { DisplayName = "OpenAI" }]
};
Assert.Equal("", helper.CallBuildProviderSummary(status));
}
[Fact]
public void BuildProviderSummary_TwoProviders_JoinedWithSeparator()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo
{
DisplayName = "OpenAI",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 20.0 }]
},
new GatewayUsageProviderInfo
{
DisplayName = "Anthropic",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 50.0 }]
}
]
};
Assert.Equal("OpenAI: 80% left · Anthropic: 50% left", helper.CallBuildProviderSummary(status));
}
[Fact]
public void BuildProviderSummary_ThreeProviders_ShowsOverflowCount()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo
{
DisplayName = "P1",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 10.0 }]
},
new GatewayUsageProviderInfo
{
DisplayName = "P2",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 20.0 }]
},
new GatewayUsageProviderInfo
{
DisplayName = "P3",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 30.0 }]
}
]
};
var result = helper.CallBuildProviderSummary(status);
Assert.Equal("P1: 90% left · P2: 80% left · +1", result);
}
[Fact]
public void BuildProviderSummary_MissingDisplayName_FallsBackToProviderField()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo
{
Provider = "openai",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 0.0 }]
}
]
};
Assert.StartsWith("openai:", helper.CallBuildProviderSummary(status));
}
[Fact]
public void BuildProviderSummary_AllProvidersEmpty_ReturnsEmpty()
{
var helper = new GatewayClientTestHelper();
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo { DisplayName = "P1" },
new GatewayUsageProviderInfo { DisplayName = "P2" }
]
};
Assert.Equal("", helper.CallBuildProviderSummary(status));
}
[Fact]
public void BuildProviderSummary_OverflowWithOneValidProvider_ShowsOverflow()
{
var helper = new GatewayClientTestHelper();
// 3 providers but only the first has windows — included=1, but Providers.Count=3 > 2 → overflow shown
var status = new GatewayUsageStatusInfo
{
Providers =
[
new GatewayUsageProviderInfo
{
DisplayName = "P1",
Windows = [new GatewayUsageWindowInfo { UsedPercent = 10.0 }]
},
new GatewayUsageProviderInfo { DisplayName = "P2" },
new GatewayUsageProviderInfo { DisplayName = "P3" }
]
};
Assert.Equal("P1: 90% left · +1", helper.CallBuildProviderSummary(status));
}
[Fact]
public void ParseUsageCostPayload_UpdatesLegacyUsageTotals()
{