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:
parent
e46dfe7830
commit
d33e873edf
@ -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)
|
||||
|
||||
@ -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()
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user