fix(cli): count benchmark application failures (#18292)

### Summary

The CLI benchmark treated every HTTP 200 response as successful, even
when the RAGFlow response envelope reported a non-zero application code.
This caused failed API calls to inflate the benchmark success count.

- classify benchmark responses using both HTTP status and the response
envelope code
- preserve successful plain-text HTTP 200 endpoints such as ping
- cover HTTP 200 application failures with an in-process HTTP regression
test
This commit is contained in:
Ngo Quoc Viet
2026-08-21 20:31:52 +08:00
committed by GitHub
parent 6483f84d0c
commit 9dc3a5bfba
2 changed files with 127 additions and 2 deletions
+23 -2
View File
@@ -22,8 +22,10 @@ import (
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
ce "ragflow/internal/cli/filesystem"
"strings"
"time"
)
@@ -180,6 +182,25 @@ func (c *HTTPClient) Request(method, path string, authKind string, headers map[s
}, nil
}
func isJSONMediaType(contentType string) bool {
mediaType, _, _ := mime.ParseMediaType(contentType)
return mediaType == "application/json" || strings.HasSuffix(mediaType, "+json")
}
func benchmarkResponseSucceeded(resp *Response) bool {
if resp.StatusCode != http.StatusOK {
return false
}
result, err := resp.JSON()
if err != nil {
// Some successful endpoints, such as ping, return plain text.
return !isJSONMediaType(resp.Headers.Get("Content-Type"))
}
code, hasCode := result["code"].(float64)
return !hasCode || code == 0
}
// RequestWithIterations makes multiple HTTP requests for benchmarking
// Returns a map with "duration" (total time in seconds) and "response_list"
func (c *HTTPClient) RequestWithIterations(method, path string, authKind string, headers map[string]string, jsonBody map[string]interface{}, iterations int) (*BenchmarkResponse, error) {
@@ -195,7 +216,7 @@ func (c *HTTPClient) RequestWithIterations(method, path string, authKind string,
response.Code = resp.StatusCode
response.Duration = totalDuration
if response.Code == 0 {
if benchmarkResponseSucceeded(resp) {
response.SuccessCount = 1
} else {
response.FailureCount = 1
@@ -264,7 +285,7 @@ func (c *HTTPClient) RequestWithIterations(method, path string, authKind string,
response.Code = 0
response.Duration = totalDuration
for _, resp := range responseList {
if resp.StatusCode == 200 {
if benchmarkResponseSucceeded(resp) {
response.SuccessCount++
} else {
response.FailureCount++
+104
View File
@@ -0,0 +1,104 @@
//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package cli
import (
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
)
func newTestHTTPClient(t *testing.T, handler http.Handler) *HTTPClient {
t.Helper()
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
serverURL, err := url.Parse(server.URL)
if err != nil {
t.Fatal(err)
}
host, portText, err := net.SplitHostPort(serverURL.Host)
if err != nil {
t.Fatal(err)
}
port, err := strconv.Atoi(portText)
if err != nil {
t.Fatal(err)
}
client := NewHTTPClient()
client.Host = host
client.Port = port
client.client = server.Client()
return client
}
func TestRequestWithIterationsCountsApplicationErrorsAsFailures(t *testing.T) {
client := newTestHTTPClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"code": 100, "message": "request failed"}`))
}))
result, err := client.RequestWithIterations(http.MethodGet, "/failure", "web", nil, nil, 3)
if err != nil {
t.Fatalf("RequestWithIterations() error = %v", err)
}
if result.SuccessCount != 0 || result.FailureCount != 3 {
t.Fatalf("successes = %d, failures = %d; want 0 successes and 3 failures", result.SuccessCount, result.FailureCount)
}
}
func TestRequestWithIterationsCountsMalformedJSONAsFailures(t *testing.T) {
for _, contentType := range []string{
"application/json",
"application/problem+json; charset=utf-8",
} {
t.Run(contentType, func(t *testing.T) {
client := newTestHTTPClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", contentType)
_, _ = w.Write([]byte(`{"code":`))
}))
result, err := client.RequestWithIterations(http.MethodGet, "/malformed", "web", nil, nil, 3)
if err != nil {
t.Fatalf("RequestWithIterations() error = %v", err)
}
if result.SuccessCount != 0 || result.FailureCount != 3 {
t.Fatalf("successes = %d, failures = %d; want 0 successes and 3 failures", result.SuccessCount, result.FailureCount)
}
})
}
}
func TestRequestWithIterationsPreservesPlainTextSuccess(t *testing.T) {
client := newTestHTTPClient(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte("pong"))
}))
result, err := client.RequestWithIterations(http.MethodGet, "/ping", "none", nil, nil, 3)
if err != nil {
t.Fatalf("RequestWithIterations() error = %v", err)
}
if result.SuccessCount != 3 || result.FailureCount != 0 {
t.Fatalf("successes = %d, failures = %d; want 3 successes and 0 failures", result.SuccessCount, result.FailureCount)
}
}