-
Notifications
You must be signed in to change notification settings - Fork 472
Reduce BenchmarkValidation overhead by caching safe-output field order #44090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f30b546
4a6399c
69b6683
81dfd34
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,9 @@ var ( | |
| compiledToolSchemasOnce sync.Once | ||
| compiledToolSchemas map[string]toolSchemaEntry | ||
| compiledToolSchemasErr error | ||
|
|
||
| sortedSafeOutputFieldNamesOnce sync.Once | ||
| sortedSafeOutputFieldNames []string | ||
| ) | ||
|
|
||
| func getCompiledToolSchemas() (map[string]toolSchemaEntry, error) { | ||
|
|
@@ -97,6 +100,13 @@ func getCompiledToolSchemas() (map[string]toolSchemaEntry, error) { | |
| return compiledToolSchemas, compiledToolSchemasErr | ||
| } | ||
|
|
||
| func getSortedSafeOutputFieldNames() []string { | ||
| sortedSafeOutputFieldNamesOnce.Do(func() { | ||
| sortedSafeOutputFieldNames = sliceutil.SortedKeys(safeOutputFieldMapping) | ||
| }) | ||
| return sortedSafeOutputFieldNames | ||
| } | ||
|
Comment on lines
+104
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] 💡 Suggested fixReturn a copy, or document the contract that callers must not mutate the result: // getSortedSafeOutputFieldNames returns the sorted keys of safeOutputFieldMapping.
// The returned slice MUST NOT be modified by the caller.
func getSortedSafeOutputFieldNames() []string {
sortedSafeOutputFieldNamesOnce.Do(func() {
sortedSafeOutputFieldNames = sliceutil.SortedKeys(safeOutputFieldMapping)
})
return sortedSafeOutputFieldNames
}The existing call sites only range over the slice, so they are safe today — but this is an implicit contract that new callers can easily violate. A doc comment is low-cost insurance. @copilot please address this. |
||
|
|
||
| // validateSafeOutputsSamples validates every `samples` entry on every | ||
| // enabled safe-output handler against the corresponding MCP tool's inputSchema. | ||
| // Sample sidecar fields (e.g. `patch`) are stripped before validation. Returns | ||
|
|
@@ -107,7 +117,7 @@ func validateSafeOutputsSamples(config *SafeOutputsConfig) error { | |
| return nil | ||
| } | ||
|
|
||
| fieldNames := sliceutil.SortedKeys(safeOutputFieldMapping) | ||
| fieldNames := getSortedSafeOutputFieldNames() | ||
| samplesValidationLog.Printf("Validating safe-outputs samples across %d candidate fields", len(fieldNames)) | ||
|
|
||
| for _, fieldName := range fieldNames { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,10 +4,25 @@ import ( | |
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/github/gh-aw/pkg/sliceutil" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestGetSortedSafeOutputFieldNames_MatchesSortedKeys(t *testing.T) { | ||
| t.Parallel() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The test asserts cache correctness but not the benchmark regression it was designed to fix. There is no regression test or benchmark that would catch a future reintroduction of the per-call sort. 💡 SuggestionAdd a func BenchmarkGetSortedSafeOutputFieldNames(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
_ = getSortedSafeOutputFieldNames()
}
// After warm-up the cache is primed; allocs should be 0
}This would have caught the original regression directly. @copilot please address this. |
||
|
|
||
| expected := sliceutil.SortedKeys(safeOutputFieldMapping) | ||
| first := getSortedSafeOutputFieldNames() | ||
| second := getSortedSafeOutputFieldNames() | ||
|
|
||
| assert.Equal(t, expected, first) | ||
| assert.Equal(t, expected, second) | ||
| if assert.NotEmpty(t, first) && assert.NotEmpty(t, second) { | ||
| assert.Equal(t, &first[0], &second[0], "expected cached field names to reuse the same backing slice") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The pointer-identity assertion ( 💡 Suggested alternativeThe meaningful invariant is idempotent sorted order, which value-equality already captures: // first two asserts already cover this
assert.Equal(t, first, second)If you want to document the zero-alloc property, a @copilot please address this. |
||
| } | ||
|
Comment on lines
+21
to
+23
|
||
| } | ||
|
|
||
| // TestValidateSafeOutputsSamples_Valid covers the happy path for the | ||
| // strict schema validation of samples entries. We use create_issue (no | ||
| // sidecars, just title/body) and create_pull_request (with the `patch` sidecar | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/codebase-design] The new
sync.Oncevariables are grouped withcompiledToolSchemas*in the samevarblock, but they serve a logically different concern (field-name ordering vs. schema compilation). Consider adding a short comment separating them, following the pattern already used forcompiledToolSchemas.💡 Suggestion
This mirrors the doc-comment convention already in place for
compiledToolSchemas(line 58) and makes each cache group independently navigable.@copilot please address this.