perf(acceptance): 流式生成唯一图片输入
避免千请求验收重复复用同一图片内容而争用同一资产记录,使每个任务走真实独立上传与物化链路。\n\nGemini 请求体改为流式 Base64 编码,降低压测客户端的大对象内存峰值;新增尺寸、哈希唯一性和请求体还原测试。\n\n验证:Go 全量测试、go vet、迁移安全检查、bash -n、ShellCheck、gofmt、git diff --check 均通过。
This commit is contained in:
@@ -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]...)
|
||||
|
||||
Reference in New Issue
Block a user