perf(acceptance): 流式生成唯一图片输入

避免千请求验收重复复用同一图片内容而争用同一资产记录,使每个任务走真实独立上传与物化链路。\n\nGemini 请求体改为流式 Base64 编码,降低压测客户端的大对象内存峰值;新增尺寸、哈希唯一性和请求体还原测试。\n\n验证:Go 全量测试、go vet、迁移安全检查、bash -n、ShellCheck、gofmt、git diff --check 均通过。
This commit is contained in:
2026-07-31 04:55:31 +08:00
parent 1bb4b2ab12
commit 7b2ab786a2
2 changed files with 97 additions and 15 deletions
+45 -15
View File
@@ -247,20 +247,6 @@ func runGemini(
realUpstream bool,
) phaseResult {
startedAt := time.Now()
input := paddedPNG(inputBytes)
requestBody, _ := json.Marshal(map[string]any{
"contents": []any{map[string]any{
"role": "user",
"parts": []any{
map[string]any{"text": "将参考图编辑为蓝色赛博朋克风格,保留主体结构"},
map[string]any{"inlineData": map[string]any{
"mimeType": "image/png",
"data": base64.StdEncoding.EncodeToString(input),
}},
},
}},
"generationConfig": map[string]any{"responseModalities": []any{"IMAGE"}},
})
expectedHash := ""
if expectedOutputBytes > 0 {
sum := sha256.Sum256(paddedPNG(expectedOutputBytes))
@@ -287,7 +273,16 @@ func runGemini(
defer func() { <-slots }()
requestStarted := time.Now()
endpoint := opts.gateways[index%len(opts.gateways)] + "/v1beta/models/" + url.PathEscape(opts.geminiModel) + ":generateContent"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(requestBody))
requestBody := streamGeminiRequestBody(paddedPNGVariant(inputBytes, index+1))
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
endpoint,
requestBody,
)
if err != nil {
_ = requestBody.Close()
}
if err == nil {
opts.setHeaders(req, index, realUpstream)
req.Header.Set("Content-Type", "application/json")
@@ -344,6 +339,34 @@ func runGemini(
return phaseResult{report: result, latencies: latencies, err: firstErr}
}
func streamGeminiRequestBody(input []byte) io.ReadCloser {
reader, writer := io.Pipe()
go func() {
closeWithError := func(err error) {
_ = writer.CloseWithError(err)
}
if _, err := io.WriteString(writer, `{"contents":[{"role":"user","parts":[{"text":"将参考图编辑为蓝色赛博朋克风格,保留主体结构"},{"inlineData":{"mimeType":"image/png","data":"`); err != nil {
closeWithError(err)
return
}
encoder := base64.NewEncoder(base64.StdEncoding, writer)
if _, err := encoder.Write(input); err != nil {
closeWithError(err)
return
}
if err := encoder.Close(); err != nil {
closeWithError(err)
return
}
if _, err := io.WriteString(writer, `"}}]}],"generationConfig":{"responseModalities":["IMAGE"]}}`); err != nil {
closeWithError(err)
return
}
_ = writer.Close()
}()
return reader
}
func runVideo(
ctx context.Context,
client *http.Client,
@@ -778,6 +801,10 @@ func percentileMilliseconds(values []time.Duration, quantile float64) float64 {
}
func paddedPNG(size int) []byte {
return paddedPNGVariant(size, 0)
}
func paddedPNGVariant(size int, variant int) []byte {
base, _ := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=")
if size <= len(base) {
return base
@@ -790,6 +817,9 @@ func paddedPNG(size int) []byte {
chunk := make([]byte, 12+paddingLength)
binary.BigEndian.PutUint32(chunk[:4], uint32(paddingLength))
copy(chunk[4:8], []byte("teST"))
if variant != 0 && paddingLength >= 8 {
binary.BigEndian.PutUint64(chunk[8:16], uint64(variant))
}
binary.BigEndian.PutUint32(chunk[8+paddingLength:], crc32.ChecksumIEEE(chunk[4:8+paddingLength]))
out := make([]byte, 0, size)
out = append(out, base[:iendOffset]...)